First we need to set up the environment and load the packages we will
use for this workshop.
library(Seurat): Loads the Seurat package, which is a
comprehensive toolkit for single-cell RNA sequencing and spatial
transcriptomics data analysis. It provides a wide range of functions for
data preprocessing, normalization, clustering, dimensionality reduction,
and visualization. Explore documentation here: https://satijalab.org/seurat/
library(ggplot2): Loads the ggplot2 package, a powerful and
flexible system for creating static visualizations in R. Explore
documentation here: https://ggplot2.tidyverse.org/
library(scCustomize): Loads the scCustomize package, which
provides custom functions and themes to enhance the visualization and
analysis capabilities of single-cell and spatial transcriptomics data,
often in conjunction with Seurat. Explore documentation here: https://samuel-marsh.github.io/scCustomize/
library(readr): Loads readr package for fast and friendly
reading of rectangular data, such as CSV files, into R.
library(pheatmap): Loads pheatmap package, which is for
creating pretty heatmaps, offering better control over heatmap
customization compared to base R.
library(matrixStats): matrixStats provides highly optimized
functions for matrix operations, particularly useful for computing row
and column summaries.
library(spdep): spdep stands for Spatial Dependence and
Spatial Autocorrelation, and it provides functions for spatial data
analysis, including spatial weights generation, spatial autocorrelation
statistics, and spatial regression.
library(geojsonR) The geojsonR library is used for handling
GeoJSON data in R. GeoJSON is a format for encoding a variety of
geographic data structures using JavaScript Object Notation (JSON). It
is sometimes used as a format for storing cell segmentation
boundaries.
library(Seurat)
library(ggplot2)
library(scCustomize)
library(readr)
library(pheatmap)
library(matrixStats)
library(spdep)
library(geojsonR)
Sets the path to the directory containing the Xenium output data -
this is the directory where all of the outputs are stored.
data_dir <- "/project/shared/spatial_data_camp/datasets/DATASET2/XENIUM_COLORECTAL_CANCER/"
ReadXenium reads Xenium spatial transcriptomics data from a
specified directory using a Seurat wrapper function that supports this
data format. Xenium data typically includes expression matrices and
spatial coordinates, along with other information about cell centroids
and segmentations and coordinates of individual transcripts.
data_dir: The path to the directory containing the Xenium
data, set in the previous step. outs = c(“matrix”, “microns”):
Specifies the outputs to read from the data directory. matrix refers to
summarised cell by gene matrix and microns refers to individual
transcript coordinates.
type = c(“centroids”, “segmentations”): Indicates the types
of spatial information to include - here, we are reading ib both cell
centroid coordinates and cell boundary segmentations.
data <- ReadXenium(data_dir, outs = c("matrix", "microns"), type=c("centroids", "segmentations"))
10X data contains more than one type and is being returned as a list containing matrices of each type.
|--------------------------------------------------|
|==================================================|
This provides us a list of data:
names(data)
[1] "matrix" "microns" "centroids" "segmentations"
Matrix is further split into gene expression matrix and various
control probes and codewords. Different platforms and platform versions
include different control probes. As this will vary, it’s important to
check and understand what the specific controls in your own data
are.
Here, negative control probes are probes that are added to the
reaction but target non-biological sequences and should not bind any
tissue RNA. Negative control codewords are valid codewords, but no
probes with that codeword added to the reaction. This effectively tells
us how good the transcript calling algorithm is.
names(data$matrix)
[1] "Gene Expression" "Negative Control Codeword" "Negative Control Probe"
[4] "Unassigned Codeword"
Read in additional information about the cells - this gives us
pre-calculated information, for example segmented cell or nucleus size
for each cell.
cell_meta_data <- read.csv(file.path(data_dir, "cells.csv.gz"))
rownames(cell_meta_data) <- cell_meta_data$cell_id
head(cell_meta_data)
We will start by creating a basic seurat object from the data.
CreateSeuratObject function initializes a Seurat object
using the provided gene expression matrix and optional metadata.
counts: The gene expression matrix, which contains the raw
count data for each gene in each cell. data$matrix[[“Gene
Expression”]]: Specifies the gene expression matrix extracted from
the loaded Xenium data. Here, we leave out the control probes for
now.
assay: The name of the assay - you can call it anything you
like. Here, we go with “XENIUM”.
meta.data: Metadata associated with the cells or spots.
Here, we add the cell statistics we read in earlier as
cell_meta_data.
By printing the seurat object, we can see that we read in ~
30,000 cells with measures for 325 genes
seurat <- CreateSeuratObject(counts = data$matrix[["Gene Expression"]],
assay = "XENIUM",
meta.data = cell_meta_data)
seurat
An object of class Seurat
325 features across 647524 samples within 1 assay
Active assay: XENIUM (325 features, 0 variable features)
1 layer present: counts
Adding spatial coordinates to a Seurat object allows for spatially
resolved analysis and visualization. This requires creating objects for
centroids and segmentations we read in earlier, and then integrating
these with the main Seurat object.
CreateFOV: This function creates a field of view (FOV)
object that includes spatial information about the centroids,
segmentations, and molecule coordinates. An FOV can be the entire slide,
or a selected region within a slide - i.e. it does not need to have
entries for all the cells in the seurat object.
coords: A list containing the centroids and/or segmentation
data. For larger datasets, it can be quicker to only load centroids, as
this minimises the amount of data points.
centroids = CreateCentroids(data\(centroids)*: Creates a centroids object from the
centroid data in the Xenium dataset. *segmentation =
CreateSegmentation(data\)segmentations): Creates a
segmentation object from the segmentation data in the Xenium
dataset.
type = c(“segmentation”, “centroids”): Specifies the types
of spatial data being included, which are segmentation and centroid
data.
molecules = data$microns: The spatial coordinates of
individual transcripts/molecules in the data. This is optional - for
larger datasets, skipping transcript coordinates can be a good idea.
seurat[[“COLON”]] <- coords: Adds the created FOV object
to the Seurat object under the new FOV name “COLON”. This can be named
(almost) anything - but, avoid using underscores as this can create some
unexpected behaviours later.
TIP: LoadXenium() is a wrapper that would load in both cell
counts matrix and spatial coordinates in one function, simplifying these
steps. However, in situ platforms are evolving at a very fast
rate and there are constant changes on how the data is stored, in
particular for file formats for cell segmentation and coordinates. Here,
we have broken down the steps to show how to assemble an in situ seurat
object from the key components, in case the platform specific readers
don’t work for your specific data.
coords <- CreateFOV(coords = list(centroids = CreateCentroids(data$centroids),
segmentation = CreateSegmentation(data$segmentations)),
type = c("segmentation", "centroids"),
molecules = data$microns,
assay = "XENIUM")
seurat[["COLONC2"]] <- coords
Inspect the object - now, you can see we have added a spatial field
of view:
To subset the object


Adding control probes and codewords as separate assays in the Seurat
object allows for the tracking and analysis of technical artifacts and
noise within your spatial transcriptomics data, while keeping these
outputs separate from the main biological gene expression values.
Unassigned codewords are unused codewords. There is
no probe in a particular gene panel that will generate the codeword.
Negative control probes are probes that exist in the
panels but target non-biological sequences. They can be used to assess
the specificity of the assay.
Negative control codewords are codewords in the
codebook that do not have any probes matching that code. They are chosen
to meet the same requirements as regular codewords and can be used to
assess the specificity of the decoding algorithm.
seurat[["Negative.Control.Codeword"]] <- CreateAssayObject(counts = data$matrix[["Negative Control Codeword"]])
Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')
seurat[["Negative.Control.Probe"]] <- CreateAssayObject(counts = data$matrix[["Negative Control Probe"]])
Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')
seurat[["Unassigned.Codeword"]] <- CreateAssayObject(counts = data$matrix[["Unassigned Codeword"]])
Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')Warning: Feature names cannot have underscores ('_'), replacing with dashes ('-')
subset an object
seurat #read the object
An object of class Seurat
541 features across 647524 samples within 4 assays
Active assay: XENIUM (325 features, 0 variable features)
1 layer present: counts
3 other assays present: Negative.Control.Codeword, Negative.Control.Probe, Unassigned.Codeword
2 spatial fields of view present: COLONC2 CRC2
Let’s start with some basic QC and visualisation of the data.
In Seurat, in situ spatial transcriptomics counterpart
functions to ‘SpatialDimPlot’ and ‘SpatialFeaturePlot’
we covered yesterday are called ‘ImageFeaturePlot’ and
‘ImageDimPlot’. These have additional functionality to plot
cell segmentations and individual transcript coordinates, but otherwise
function exactly the same as the sequencing based ST counterparts.
First, lets visualise the total transcripts detected per cell.
As in scRNA-Seq data, this is the most basic measure of overall
signal and how well the data looks.
Unlike in scRNA-Seq data or unbiased sequencing-based ST, these
measures are also very heavily dependent not only on the total RNA
quantity of each cell and tissue quality, but also on the target panel
used for the experiment. Under-represented cell types will naturally
yield fewer transcripts. Finally, the quality of cell segmentation also
plays a role.
In this case, we can see that there are areas with higher and lower
total transcripts detected.
Understanding your tissue and target panel here is important to
delineate where these differences are biological and where they may be
technical.


Similarly, we can visualise the total number of gene detected per
cell. You can see that this is a bit less variable across tissue.
This can also suggest that there cells at the top of the epithelial
crypts in this sample with genes detected at high copy number than the
rest of the tissue.
ImageFeaturePlot(seurat_CRC2, "nFeature_XENIUM", axes = T) + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

This code examines the distribution of the number of features (genes)
detected per cell in the Seurat object using a density plot and
calculates specific quantiles of this distribution. This is important
for understanding the variability and distribution of detected features,
which can help identify potential issues such as low-quality cells and
determine any filtering thresholds that may need to be applied.
If you’re coming from scRNA-Seq work, these low numbers probably look
very alarming. How can you possibly work with 31 median genes per
cell?
Unlike scRNA-Seq data and sequencing-based ST, both gene dropouts and
noise are much, much lower in in situ ST data.
We are also working with 100-fold fewer targetted genes.
quantile(seurat_CRC2$nFeature_XENIUM, c(0.01, 0.1, 0.5, 0.9, 0.99))
1% 10% 50% 90% 99%
5 14 33 56 76
Using ImageFeaturePlot to visualize the cell area in spatial
transcriptomics data allows us to examine the spatial organization and
potential heterogeneity of cell sizes within your tissue sample.
Why do we get such a difference in spatial distribution of
cell sizes?
This could be due to biological differences between small and large
cells - e.g. small cells like T-cells.
However, here the signal correlates with areas of low
cellularisation. Therefore, it is likely this is an artefact of nuclei
expansion in cell segmentation.
What is Nuclei Expansion?
Nuclei expansion in cell segmentation refers to the process of
enlarging the segmented nuclei regions to approximate the boundaries of
the entire cells. This technique is used to better represent the actual
cell boundaries when only the nuclei have been explicitly segmented/we
only have DAPI and no additional cell boundary staining. The primary
goal is to provide a more accurate estimation of the cellular area,
which is crucial for various downstream analyses in spatial
transcriptomics and single-cell studies. In this case, nuclei expansion
is constrained either by maximum distance or other nearby cells - so,
where there are no other nearby cells to “bump into”, the expansion
generates artificially bigger cells.
ImageFeaturePlot(seurat_CRC2, "cell_area", axes = T) + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

We can further check that this is likely the case by plotting the
ratio between nuclei and total cell area. We can see that there is a
very big decrease in percentage of cell area occupied by nucleus in
areas of low cell density.
The cell-to-nucleus area ratio can also potentially provide insights
into cell morphology, cell type and potential changes in cellular states
or conditions. For example, T-Cells can often be quite well identified
by this variable alone, as they have a small cytoplasm volume. However,
without a cell boundary stain, this metric mainly captures segmentation
artefacts, so be careful about over-interpretation!
ImageFeaturePlot(seurat_CRC2, "cell_nucleus_ratio") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

If we look at the distribution, we see that we have a big tail end of
overly large cells.

In this case, we can see that as expected, there is generally a
correlation between cell area and transcript detection rate.
However, we also have a group of cells where this is not the case -
very large cells but relatively few transcripts. These cells are mainly
submucosal stromal cells which are very poorly covered by the panel 10x
have used.


We can create a filter to remove the overly large cells from the
analysis.
quantile(seurat$cell_area, 0.99): Calculates the 99th
percentile of the cell_area values in the Seurat object. This value
serves as a threshold to identify the largest 1% of cells - but what is
a sensible threshold, if any, depends on your tissue.
seurat\(cell_area <
quantile(seurat\)cell_area, 0.99): Compares each cell’s area
to the 99th percentile threshold. The result is a logical vector where
each element is TRUE if the corresponding cell’s area is less than the
99th percentile and FALSE otherwise.
seurat[[“SIZE_FILTER_LARGE”]]: Creates a new metadata field
named SIZE_FILTER_LARGE in the Seurat object, storing the logical
vector.
seurat_CRC2[["SIZE_FILTER_LARGE"]] <- seurat_CRC2$cell_area < quantile(seurat_CRC2$cell_area, .99)
Now we can use ImageDimPlot to visualise the cells which
have been flagged for removal.
We can see that these are mostly in the submucosa region.
How do different thresholds behave? Is there a more
appropriate one to use? Is any necessary at all?
ImageDimPlot(seurat_CRC2, group.by="SIZE_FILTER_LARGE")


We can use the same approach to create a filter for segmented cells
which are very small and likely segmentation arfetacts.
quantile(seurat$cell_area, 0.01): Calculates the 1st
percentile of the cell_area values in the Seurat object. This value
serves as a threshold to identify the smallest 1% of cells.
seurat\(cell_area >
quantile(seurat\)cell_area, 0.01): Compares each cell’s area
to the 1st percentile threshold. The result is a logical vector where
each element is TRUE if the corresponding cell’s area is greater than
the 1st percentile and FALSE otherwise.
seurat[[“SIZE_FILTER_SMALL”]]: Creates a new metadata field
named SIZE_FILTER_SMALL in the Seurat object, storing the logical
vector.
seurat_CRC2[["SIZE_FILTER_SMALL"]] <- seurat_CRC2$cell_area > quantile(seurat_CRC2$cell_area, .01)
Now we can use ImageDimPlot to visualise the cells which
have been flagged for removal.
We can see that these are more scattered throughout the tissue - but
there may be more in the follicular regions.
How do different thresholds behave? Is there a more
appropriate one to use? Is any necessary at all?


We can check how these values correlate with gene detection rate.
If we filter out small cells, we will remove cells with low numbers
of genes detected.
If we filter out large cells, this is not that biased towards overly
large counts, as we saw before.

Adjusting the threshold for what is considered a “small cell” can
have significant implications for your analysis, especially in areas
with specific cell types such as T-cells, which are small and densely
packed in follicular regions. This example demonstrates how changing the
threshold to the 10th percentile affects the filtering. In this case, we
would probably filter out a lot of good cells that we don’t want to
lose! So, be careful when looking at these types of QC metrics!
seurat_CRC2[["SIZE_FILTER_SMALL"]] <- seurat_CRC2$cell_area > quantile(seurat_CRC2$cell_area, .1)
ImageDimPlot(seurat_CRC2, group.by="SIZE_FILTER_SMALL")

Lets set this back to the original 1% threshold.
seurat_CRC2[["SIZE_FILTER_SMALL"]] <- seurat_CRC2$cell_area > quantile(seurat_CRC2$cell_area, .01)
The most important filter is the overall transcript detection. Empty
cells or cells with very low transcript count cannot be taken forward
for clustering analysis and it is extremely difficult to identify what
they may be. Here, we set a threshold of minimum 15 transcripts. This
seems quite low - for data from in situ platforms with low
noise (Xenium, Merfish, Merscope), this is generally enough to cluster
and identify cell types. If your data has more noise (e.g. CosMx), a
higher threshold is more appropriate.
seurat\(nCount_XENIUM >= 15*:
Compares each cell's transcript count to the threshold of 15. The
result is a logical vector where each element is TRUE if the
corresponding cell has at least 15 transcripts and FALSE otherwise.
*seurat\)TRANSCRIPT_FILTER: Creates a new metadata field
named TRANSCRIPT_FILTER in the Seurat object, storing the logical
vector.
seurat_CRC2$TRANSCRIPT_FILTER <- seurat_CRC2$nCount_XENIUM >= 15
And we can visualise the cells that we would lose.
We see that we disproportionately would filter out more cells from
some regions than others. As pointed out previously, this is likely due
to a combination of gene panel coverage in some regions and very small
cells in densely packed regions like follicles.
ImageDimPlot(seurat_CRC2, group.by="TRANSCRIPT_FILTER")

Finally, visualizing the counts of negative control codewords,
negative control probes, and unassigned codewords helps identify and
understand technical artifacts and background noise in your spatial
transcriptomics data.
Here, we can see that all control probes and codewords produce yield
very little signal, suggesting our data is good quality!
In some cases, high amount of autoflourescence is the cells/tissue
can sometimes generate false positive signal and this should be filtered
out.
ImageFeaturePlot(seurat_CRC2, "nCount_Negative.Control.Codeword") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "nCount_Negative.Control.Probe") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "nCount_Unassigned.Codeword") + scale_fill_viridis_c()
Scale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

Although the negative control signal is low, we can nonetheless
create a filter to remove cells which have any, although in this case it
is probably unnecessary.
seurat_CRC2$PROBE_FILTER <- seurat_CRC2$nCount_Unassigned.Codeword == 0 &
seurat_CRC2$nCount_Negative.Control.Codeword == 0 &
seurat_CRC2$nCount_Negative.Control.Probe == 0
ImageDimPlot(seurat_CRC2, group.by="PROBE_FILTER")

Finally, we can subset the seurat object based on any/all of the
filters we have created earlier.
By combining probe, size, and transcript filters, you can retain only
the cells that meet all quality criteria, reducing the impact of
technical artifacts and noise on your analysis.
seurat_CRC2 <- subset(seurat_CRC2, PROBE_FILTER & SIZE_FILTER_LARGE & SIZE_FILTER_SMALL & TRANSCRIPT_FILTER)
Warning: Not validating FOV objectsWarning: Not validating Centroids objectsWarning: Not validating Centroids objectsWarning: Not validating FOV objectsWarning: Not validating Centroids objectsWarning: Not validating FOV objectsWarning: Not validating FOV objectsWarning: Not validating FOV objectsWarning: Not validating Seurat objects
Lets examine the cleaned up object - we have lost a few thousand
cells from the analysis.

Data Normalisation
The SCTransform function in Seurat is used for normalizing
single-cell RNA-seq and spatial transcriptomics data. This method models
the gene expression counts using a regularized negative binomial
regression and removes technical noise while preserving biological
variability. The clip.range parameter is used to limit the
range of the transformed values, which can help stabilize downstream
analyses by limiting the influence of extreme values.
seurat_CRC2 <- SCTransform(seurat_CRC2, assay = "XENIUM", clip.range = c(-10, 10))
Running SCTransform on assay: XENIUM
Running SCTransform on layer: counts
vst.flavor='v2' set. Using model with fixed slope and excluding poisson genes.
Variance stabilizing transformation of count matrix of size 325 by 73985
Model formula is y ~ log_umi
Get Negative Binomial regression parameters per gene
Using 320 genes, 5000 cells
Found 38 outliers - those will be ignored in fitting/regularization step
Second step: Get residuals using fitted parameters for 325 genes
Computing corrected count matrix for 325 genes
Calculating gene attributes
Wall clock passed: Time difference of 21.71334 secs
Determine variable features
Centering data matrix
|
| | 0%
|
|=================================================================================| 100%
Getting residuals for block 1(of 15) for counts dataset
Getting residuals for block 2(of 15) for counts dataset
Getting residuals for block 3(of 15) for counts dataset
Getting residuals for block 4(of 15) for counts dataset
Getting residuals for block 5(of 15) for counts dataset
Getting residuals for block 6(of 15) for counts dataset
Getting residuals for block 7(of 15) for counts dataset
Getting residuals for block 8(of 15) for counts dataset
Getting residuals for block 9(of 15) for counts dataset
Getting residuals for block 10(of 15) for counts dataset
Getting residuals for block 11(of 15) for counts dataset
Getting residuals for block 12(of 15) for counts dataset
Getting residuals for block 13(of 15) for counts dataset
Getting residuals for block 14(of 15) for counts dataset
Getting residuals for block 15(of 15) for counts dataset
Centering data matrix
|
| | 0%
|
|=================================================================================| 100%
Finished calculating residuals for counts
Set default assay to SCT
Principal Component Analysis (PCA) is a dimensionality reduction
technique used to identify the primary axes of variation in
high-dimensional data. In the context of spatial transcriptomics, PCA
helps to reduce the complexity of the data while preserving the most
important patterns of variation.
TIP: If your target panel is very small, you can skip this step and
carry out clustering analysis directly on gene expression. This can
sometimes help with achieving better clustering results.
seurat_CRC2 <- RunPCA(seurat_CRC2)
PC_ 1
Positive: IGFBP7, THBS1, TIMP3, DPYSL3, CTSB, MAF, IFITM1, CYBB, VCAN, ETS1
ANXA1, CXCR4, PLXND1, CLU, APOE, TRAC, RPS4Y1, SERPINA1, IL7R, MS4A7
RNASE1, CD79A, SOCS3, FZD7, TRBC2, DEPP1, CD14, CD3E, CCL5, CD2
Negative: CD24, SLC12A2, RRM2, HMGB2, TYMS, PPP1R1B, EPHB3, CDCA7, CA2, FERMT1
SOX9, STMN1, PCLAF, C1QBP, REG4, AQP1, CMBL, MKI67, TK1, CEACAM5
EGFR, IMPDH2, S100P, SMOC2, CREB3L1, GATA2, UBE2C, MUC12, TUBA1A, LGR5
PC_ 2
Positive: CTSB, APOE, CYBB, RNASE1, MS4A7, SERPINA1, CD14, C1QC, C1QA, CD163
C1QB, FYB1, CCL4, MAF, CCL5, IL7R, GPR183, CXCR4, CD83, TRBC2
CD8A, TNFSF13B, CD2, CD3E, TRAC, GZMA, CTLA4, PLXND1, TIGIT, CD3D
Negative: THBS1, IGFBP7, DPYSL3, TIMP3, VCAN, FZD7, ALDH1B1, AQP1, DEPP1, CLU
CD24, RUNX1T1, CES1, SELENOM, CDKN2B, IFITM1, CPE, EPHB3, FRZB, HMGB2
CKAP4, RRM2, EGFR, MEIS2, TUBA1A, IMPDH2, TYMS, CA2, SLC12A2, C1QBP
PC_ 3
Positive: MS4A1, TRBC2, TRAC, CD2, CD3E, CXCR4, CD8A, CCL5, GZMK, CD79A
SPOCK2, IL7R, CTLA4, GZMA, CD3G, ETS1, TIGIT, CD3D, KLRB1, CD6
BANK1, CST7, LTB, NKG7, SPIB, CD5, LRMP, ITK, TRBC1, FOXP3
Negative: THBS1, CTSB, IGFBP7, RNASE1, APOE, CD14, MS4A7, CYBB, C1QC, SERPINA1
TIMP3, PLXND1, C1QA, CD163, C1QB, VCAN, ALDH1B1, FZD7, DPYSL3, TUBA1A
CD24, AQP1, CPE, CES1, SLC12A2, DEPP1, RUNX1T1, SOCS3, CA2, CEACAM5
PC_ 4
Positive: CD79A, MS4A1, CLU, SEC11C, BANK1, CXCR4, LRMP, SPIB, FKBP11, TNFRSF17
DERL3, CD79B, RGS13, TCL1A, PRDX4, SMIM14, FCRL1, IRF8, SELENOK, PAX5
CYBB, CXCR5, CD83, LTB, SELL, FCER2, GPR183, C2orf88, MS4A7, DPYSL3
Negative: CCL5, CD8A, GZMA, CD2, CD3E, TRBC2, TRAC, CTLA4, CD3G, CCL4
NKG7, CD3D, THBS1, TIGIT, IGFBP7, KLRB1, GZMK, CST7, CD6, SPOCK2
IL7R, MAF, TIMP3, GNLY, FOXP3, CD8B, ITK, CD5, CD24, ID2
PC_ 5
Positive: IGFBP7, TIMP3, IFITM1, PLXND1, AQP1, ETS1, CPE, SEC11C, SOCS3, FKBP11
CD79A, PRDX4, VCAN, DERL3, FRZB, TNFRSF17, LEF1, SELENOK, LYVE1, ODF2L
ROBO1, CDKN2B, CKAP4, GIMAP7, ANXA1, CA2, TUBA1A, AFAP1L2, TNFRSF25, RNASE1
Negative: THBS1, CLU, DPYSL3, ALDH1B1, FZD7, CES1, SELENOM, MS4A1, MAOB, MEIS2
CYBB, RUNX1T1, CEACAM5, TRAC, TRBC2, SPIB, CD14, CD2, TCL1A, MS4A7
MAF, LTB, CD3G, BANK1, CD8A, DEPP1, CTLA4, CD3E, TIGIT, NOVA1
As before, we can visualise how much variation is captured by each
PC.
The ElbowPlot function helps to determine the number of significant
PCs to use for downstream analyses. The plot typically shows the amount
of variance explained by each PC, and the “elbow” point indicates a
natural cutoff.
ElbowPlot(seurat_CRC2, 50)

Plotting the top genes contributing to a specific principal component
helps in understanding the biological factors driving the variation
captured by that component. This type of plot highlights the genes with
the highest loadings, which are the most influential in the principal
component analysis.
PC_Plotting(seurat_CRC2, dim_number = 1)

The FeaturePlot function in Seurat is used to visualize the
expression of a specific gene across cells in a given dimensionality
reduction space (e.g., PCA). This helps to understand how the expression
of a gene varies across the principal components.
FeaturePlot(seurat_CRC2, "CEACAM5", reduction = "pca") + scale_color_viridis_c()
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

We can also examine how various PCs are distributed spatially.
Here, we can see that high PC1 loadings enrich in follicular
structures and low PC1 loadings enrich in crypt top cells.
ImageFeaturePlot(seurat_CRC2, "PC_1") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

We can plot the expression of high (or low) loading genes to
visualise how this correlates with our dimensionality reduction.
ImageFeaturePlot(seurat_CRC2, "IGFBP7", size=.5) + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

Next, we will use the reduced dimensionality data for clustering and
cluster visualisation.
RunUMAP: Perform Uniform Manifold Approximation and
Projection (UMAP) to reduce the dimensionality of the data for
visualization. The UMAP plot reduces the high-dimensional data to two
dimensions, preserving the local and global structure of the data for
visualization. Cells that are close together in the UMAP plot are
similar in their gene expression profiles. seurat: The Seurat
object. dims = 1:20: Specifies the principal components to use
for UMAP.
FindNeighbors: Finding nearest neighbors helps to identify
cells that are similar based on their PCA scores, which is used for
clustering. seurat: The Seurat object. reduction =
“pca”: Specifies that the PCA space should be used for finding
neighbors. dims = 1:20: Specifies the principal components to
use for identifying neighbors.
FindClusters: Clustering identifies distinct groups of cells
with similar gene expression patterns. The resolution parameter controls
the granularity of the clustering. seurat: The Seurat object.
resolution = 0.7: Sets the resolution parameter for clustering.
Higher values lead to more clusters, while lower values lead to fewer
clusters.
seurat_CRC2 <- FindClusters(seurat_CRC2, resolution = 0.2)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
Number of nodes: 73985
Number of edges: 2363377
Running Louvain algorithm...
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.9173
Number of communities: 6
Elapsed time: 52 seconds
Next lets visualise the clusters - firstly, based on transcriptome
embedding.
DimPlot: Creates a scatter plot of cells in a
reduced-dimensional space, by default now using UMAP dimensionality
reduction. seurat: The Seurat object containing the
dimensionality reduction results and cluster assignments. label =
TRUE: Adds cluster labels to the plot. repel = TRUE:
Repels the labels to avoid overlapping, making the plot clearer.

And now lets plot the clusters in tissue space.
We can see that our clusters have quite nice correspondence to
distinct spatial regions.
ImageDimPlot(seurat_CRC2, size=.5)
Warning: No FOV associated with assay 'SCT', using global default FOV

As before, now we can use Seurat differential expression functions to
identify marker genes for specific cell clusters.
FindMarkers: Identifies genes that are differentially
expressed in a specified cluster compared to all other cells.
seurat: The Seurat object containing the gene expression data
and cluster identities. ident.1 = “0”: Specifies the cluster of
interest for which marker genes are to be identified. In this case,
cluster “0”. max.cells.per.ident = 500: Limits the number of
cells to be used from each cluster for the differential expression
analysis to 500. This can help to speed up the computation.
markers <- FindMarkers(seurat_CRC2, ident.1="0", max.cells.per.ident=500)
We can visualise expression of cluster specific markers using feature
plots
ImageFeaturePlot(seurat_CRC2, "CD3E", size=.5) + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "MS4A1", size=.5) + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "CEACAM5", size=.5) + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "KIT", size=.5) + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

Or, as in our sequencing ST tutorial, detect and visualise top
markers for every cluster.
markers <- FindAllMarkers(seurat_CRC2, max.cells.per.ident = 500)
Calculating cluster 0
Calculating cluster 1
Calculating cluster 2
Calculating cluster 3
Calculating cluster 4
Calculating cluster 5
scCustomize package provides a convenient helper function,
Extract_Top_Markers, to extract the top marker genes for each
cluster from the output of FindAllMarkers. This function
simplifies the process of identifying and retrieving the most
significant marker genes for analysis and visualisation.
In this case, we are extracting the top five markers per cluster.
top
[1] "DMBT1" "REG4" "MLPH" "FERMT1" "EGFR" "THBS1" "CPE"
[8] "DEPP1" "CES1" "RUNX1T1" "APOE" "C1QB" "C1QC" "C1QA"
[15] "CD14" "MS4A1" "CD7" "CTLA4" "GZMA" "TRBC2" "MUC12"
[22] "CEACAM5" "OLFM4" "CEACAM6" "CD177" "DERL3" "CPA3" "TNFRSF17"
[29] "SLC18A2" "MS4A2"
Clustered_DotPlot function from the scCustomize
package provides a convenient and visually appealing way to display
expression patterns of top marker genes across clusters using a dot
plot. This function not only plots the expression data but also clusters
the genes and groups for enhanced visual interpretation. This is an
alternative to Seurat DotPlot function.
k = 18: Determines the number of clusters for the
hierarchical clustering of genes to enhance visual separation of
expression patterns.
We can see that most clusters have unique markers, which suggests the
dataset is not over-clustered.
Clustered_DotPlot(seurat_CRC2, features = top, k=18)
[[1]]
[[2]]



Additional Spatial Visualisations
The resolution of in situ datasets is typically very high
and so it can be difficult to visualise everything in one plot. Below,
we will explore different visualisations that can help unpick and
understand the data a bit better.
To better visualise spatial distribution of clusters, sometimes it
can be useful to subset only certain groups to reduce crowding. Here, we
specifically only visualising two selected clusters.
WhichCells: Identifies cells based on specified criteria.
seurat: The Seurat object. expression = seurat_clusters
%in% c(0, 5): Logical expression to select cells belonging to
clusters 0 and 5.
This works with ImageFeaturePlot too. Try it with
some genes!
ImageDimPlot(seurat_CRC2, cells=WhichCells(seurat_CRC2, expression = seurat_clusters %in% c(0, 5)))
Warning: No FOV associated with assay 'SCT', using global default FOV

Sometimes, it can be useful to create additional fields of view of
the data - for example, zooms of specific regions. First, let’s look at
the coordinate system by plotting the data and turning on the plotting
of the axes, which are off by default to create nicer looking plots.
This gives us a rough idea on where in the coordinate system to
create any subsets or zooms of the data.
For example, if we want to zoom in on the follicle in the top right
corner, we can see that it lies roughly between 4000-5000 and 8000-9000
coordinate regions.
ImageDimPlot(seurat_CRC2, axes = T)
Warning: No FOV associated with assay 'SCT', using global default FOV

So, let’s create a new FOV with these coordinates. For this, we can
use the Crop function.
seurat[[“COLON”]]: The spatial assay to be cropped. x =
c(4200, 5000): The x-axis range for the crop. y = c(8000,
8800): The y-axis range for the crop. coords = “plot”:
Specifies the coordinate system to use (typically “plot” for spatial
coordinates).
seurat[[“ROI1”]] <- cropped: Adds the cropped region as a
new FOV named “ROI1” in the Seurat object. This could be a more
informative name, but avoid using underscores!
cropped <- Crop(seurat[["COLON"]], x = c(4200, 5000), y = c(8000, 8800), coords = "plot")
seurat[["ROI1"]] <- cropped
Now we can limit our visualisations just to this region by specifying
the name of the new FOV as an “fov” arguement.
As we are zooming in closer to the tissue, we can also switch from
plotting cell centroids (i.e. dots) by default to visualising cell
segmentation boundaries. Plotting cell boundary polygons for large FOVs
can be quite time consuming, and doesn’t provide much more detail on a
fully zoomed-out view.
ImageDimPlot(seurat, fov="ROI1", boundaries="segmentation", border.color = "black" )
We can visualise gene expression or other continous variable on the
new FOV as before.
For example, here we have MS4A1/CD20 expression, which is a B-Cell
marker. We can see it quite nicely limited to the lymphoid follicle.
ImageFeaturePlot(seurat, "MS4A1", fov="ROI1", boundaries="segmentation" , border.color = "black") + scale_fill_viridis_c()
We can also overlay the coordinates of individual molecules to the
plot. For example, here we are added some more T-cell and B-cell
specific markers.
This visualisation can be useful because molecules are stored
independently of cells and cell boundaries in Seurat. Therefore, if
there are regions where cell segmentation is not good, or if cells were
filtered out from clustering analysis due to their low quality, the
molecules will remain and can still be visualised this way.
For example, here we can see there are a few molecules of CXCR5
detected outside of cellular boundaries.
ImageFeaturePlot(seurat, "MS4A1", fov="ROI1", boundaries="segmentation", molecules=c("CXCR5", "FOXP3"), mols.size = .5, border.color = "black" ) + scale_fill_viridis_c()
Cell Type Identification
You can manually annotate your cell clusters, or you can classify
them using a reference single-cell dataset. This process is simpler than
for Visium data because our data is at the single-cell level,
establishing a one-to-one relationship without the need for spot
deconvolution.
However, our transcriptome is more limited here, and some cell types
may not be well represented. Additionally, our single-cell reference
might be missing some cell types that are not well captured by
droplet-based technologies but are present in our tissue data.
In this example, we will use a single-cell reference dataset that we
prepared earlier.
We will start by reading in the seurat RDS file.
ref <- readRDS("/project/shared/spatial_data_camp/datasets/SINGLE_CELL_REFERENCES/COLON_HC_5K_CELLS.RDS")
Examine the object:
ref
An object of class Seurat
33556 features across 5725 samples within 3 assays
Active assay: RNA (33538 features, 2000 variable features)
3 layers present: counts, data, scale.data
2 other assays present: HTO, ADT
2 dimensional reductions calculated: pca, umap
And plot the pre-computed cell clusters. We can see that here we have
quite high level annotation.

We want to evaluate how much structural information is lost in
single-cell data when limiting ourselves to the targeted gene set.
Accurate cluster prediction is challenging if the current gene set does
not adequately identify them. To do this, we will quickly re-embedd the
data using only the genes present in our spatial transcriptomics data
and keep the original cluster annotations derived from unbiased
data.
In this example, we can observe that the limited gene set does a
reasonably good job at distinguishing major cell populations. However,
it struggles to differentiate between similar cell types, such as
myofibroblasts and fibroblasts, as effectively as before.

If we visualise the specificity of the gene panel across our single
cell reference clusters, we can see that the panel coverage is mainly
concentrated across epithelial cells and T-Cells and other immune cells,
with few specific markers expressed by stromal cells.

Next, we can use the standard Seurat integration and
cross-classification workflow to transfer single-cell derived labels to
our spatial object.
Briefly, the first function identifies anchors between the reference
single-cell dataset (ref) and the query spatial dataset (seurat).
Anchors are pairs of cells that are considered similar between the
datasets. The normalization.method = “SCT” specifies that
SCTransform normalization should be used.
The second step transfers the cell type labels from the reference
dataset to the query dataset. The anchorset argument specifies the
anchors found in the previous step. The refdata = ref$CellType
argument specifies the cell type labels from the reference dataset to be
transferred. The prediction.assay = TRUE argument indicates
that the transferred labels should be stored in a new assay in the query
dataset. The weight.reduction = seurat[[“pca”]] argument
specifies the dimensionality reduction to be used for weighting the
transfer, and dims = 1:30 specifies the number of dimensions to
use.
seurat_CRC2 <- TransferData(anchorset = anchors,
refdata = ref$CellType,
prediction.assay = TRUE,
weight.reduction = seurat_CRC2[["pca"]],
query = seurat_CRC2,
dims=1:30)
Finding integration vectors
Finding integration vector weights
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Predicting cell labels
Warning: Layer counts isn't present in the assay object; returning NULL
Unfortunately, the predicted labels and spatial clusters do not
correspond clearly in all cases. This discrepancy is particularly
evident in the middle regions of the UMAP, where many cells are
predicted as epithelial cells - probably incorrectly!
How to improve this?
Ensure Good Representation of Cell Type Markers in in
situ Target Panel Most critically, before undertaking any
experiments you want to ensure that there is good representation of all
cell types in your target panel - in this case, there is not much to be
done as the data has already been generated.
Review and Refine Reference Data: Ensure that the
reference single-cell dataset is comprehensive and accurately annotated.
If certain cell types are not well represented or annotated in the
reference dataset, it can lead to misclassification.
Increase the Number of Dimensions: Increasing the
number of dimensions used in the UMAP and PCA steps might capture more
variance in the data, leading to better label transfer.
Filter and Preprocess Data: Filtering out
low-quality cells or genes and performing additional preprocessing steps
can enhance the accuracy of the transfer anchors and, consequently, the
label predictions.
Manually Annotate or Correct Predictions: In cases
where automatic label transfer is insufficient, consider manually
annotating or correcting the predictions for critical regions to ensure
accuracy.


As before, we can also visualise the predicted cell labels in tissue
space.
ImageDimPlot(seurat_CRC2, group.by = "predicted.id")
Warning: No FOV associated with assay 'NEIGHBOURHOOD100', using global default FOV


In line with non-specific predictions, we can also see that the
prediction score across these areas is lower.
Outside of stromal cells, we can also see that prediction probability
can be low in cells that embedd “between” clusters, for example between
core T-Cells and B-Cells, two populations that should be distinct.
This is often the case where cell segmentation is imperfect and
partitions transcripts in such a way that it generates “artificial”
doublets by pulling in transcripts from an adjacent cell.
FeaturePlot(seurat, "predicted.id.score")
For example, if we visualise the lineage markers for T-Cells and
B-Cells, we can see that they are often “co-expressed” in the same cells
when biologically, they should not be.
The FeatureScatter function in Seurat is used to create a
scatter plot showing the relationship between the expression levels of
two genes across all cells. This visualization helps to identify
potential correlations or patterns between the two genes.

ImageDimPlot(seurat_CRC2, boundaries="segmentation", border.color = "black" )
Warning: No FOV associated with assay 'SCT', using global default FOV

ImageDimPlot(seurat_CRC2)
Warning: No FOV associated with assay 'SCT', using global default FOV

ImageDimPlot(seurat_CRC2, group.by = "predicted.id")
Warning: No FOV associated with assay 'SCT', using global default FOV


Spatial Neighbourhood Analyis
neighbours <- FindNeighbors(as.matrix(coords[, c("x", "y")]), k.param = 20, return.neighbor=TRUE)
Computing nearest neighbors
Computing nearest neighbors
ImageDimPlot(seurat_CRC2)
Warning: No FOV associated with assay 'SCT', using global default FOV


Finding Spatially Correlated Genes
neighbours <- FindNeighbors(as.matrix(coords[, c("x", "y")]), k.param = 50)
Computing nearest neighbor graph
Computing SNN
neighbours <- FindNeighbors(as.matrix(coords[, c("x", "y")]), k.param = 50)
Computing nearest neighbor graph
Computing SNN
mt <- LayerData(seurat_CRC2, layer = "counts", assay = "XENIUM")
sum_mtx <- as.matrix(neighbours$nn %*% t(mt))
We can store the neighbourhood-aggregated values in our Seurat object
as a separate assay, which we will call “NEIGHBOURHOOD50”. We then
normalise the matrix.
seurat_CRC2 <- NormalizeData(seurat_CRC2, assay = "NEIGHBOURHOOD50")
Performing log-normalization
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
We can then apply quick correlation calculations to identify
spatially correlated features.


modules <- cutree(heatmap$tree_row, 5)
modules
AKR7A3 ANXA1 APOE BANK1 C1QA C1QB C1QBP C1QC CA2 CCL5
1 2 3 4 3 3 1 3 1 2
CD14 CD163 CD2 CD24 CD3D CD3E CD3G CD5 CD6 CD79A
3 3 2 1 2 2 2 2 2 2
CD79B CD8A CDCA7 CEACAM5 CEACAM6 CES1 CLU CMBL CPE CREB3L1
4 2 1 5 5 2 2 1 4 5
CST7 CTLA4 CTSB CXCR4 CYBB DEPP1 DERL3 DPYSL3 EGFR EPHB3
2 2 3 2 2 2 4 2 1 1
ETS1 FERMT1 FKBP11 FOXP3 FRZB FYB1 FZD7 GALNT5 GATA2 GIMAP7
2 1 4 2 2 2 2 5 1 2
GPR183 GZMA GZMK HMGB2 IFITM1 IGFBP7 IL17RB IL7R IMPDH2 ITK
2 2 2 1 2 2 1 2 1 2
KLRB1 KRTCAP3 LGR5 LRMP LTB MAF MKI67 MLPH MS4A1 MS4A7
2 1 1 2 2 2 1 1 4 3
MYH14 NKG7 PBK PCLAF PLPP2 PLXND1 PPP1R1B PTTG1 REG4 RNASE1
5 4 1 1 1 2 1 1 1 3
RNF43 ROBO1 RORA RPS4Y1 RRM2 RUNX1T1 S100P SEC11C SELENOM SERPINA1
1 2 2 2 1 2 1 4 2 3
SFXN1 SLC12A2 SMOC2 SOCS3 SOX9 SPOCK2 STMN1 THBS1 TIGIT TIMP3
1 1 1 2 1 2 1 2 2 2
TK1 TKT TNFAIP3 TNFRSF17 TNFSF13B TRAC TRBC1 TRBC2 TYMS UBE2C
1 1 2 2 2 2 2 2 1 1
VCAN
2
Lets visualize some of the detected spatially co-localizing genes.
For example, module 2 genes - we can see that CEACAM6 and AQP8 are
spatially similar, but not necessarily always expressed by the same
cells.
ImageFeaturePlot(seurat_CRC2, "CD24") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'NEIGHBOURHOOD100', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.


ImageFeaturePlot(seurat_CRC2, "ANXA1") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'NEIGHBOURHOOD100', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

seurat_CRC2 <- AddModuleScore(seurat_CRC2, features=split(names(modules), modules), assay = "SCT", nbin=3, name = "MOD" )
Visualising module scores - we can see that we have identified a
group of genes co-localising at the base of the epithelial crypts (MOD1)
and another module of genes co-localising in lymphoid follicles.
ImageFeaturePlot(seurat_CRC2, "MOD1") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "MOD2") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "MOD3") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "MOD4") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

ImageFeaturePlot(seurat_CRC2, "MOD5") + scale_fill_viridis_c()
Warning: No FOV associated with assay 'SCT', using global default FOVScale for fill is already present.
Adding another scale for fill, which will replace the existing scale.

Detecting Cellular Niches
neighbours <- FindNeighbors(as.matrix(coords[, c("x", "y")]), k.param = 100)
Computing nearest neighbor graph
Computing SNN
diag(neighbours$nn) <- 0 # dont count transcriptome of the cell itself, just neighbours
mt <- LayerData(seurat_CRC2, layer = "counts", assay = "XENIUM")
sum_mtx <- as.matrix(neighbours$nn %*% t(mt))
How is this useful? Well, now you can cluster cells not on their gene
expression values, but gene expression values of surrounding cells. This
effectively partitions cells not based on their identity, but on their
micro-environment! Using this approach, you can identify tissue
niches
Alternative approaches - you could count cell types rather than gene
expression values, but that requires you to have finalised cell
annotation for your dataset, which is not ideal. So, we do unbiased
transcriptomics approach.
How would you run this with cell types?
seurat_CRC2[["NEIGHBOURHOOD100"]] <- CreateAssayObject(t(sum_mtx))
DefaultAssay(seurat_CRC2) <- "NEIGHBOURHOOD100"
seurat_CRC2 <- NormalizeData(seurat_CRC2)
Performing log-normalization
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
seurat_CRC2 <- ScaleData(seurat_CRC2, features = rownames(seurat_CRC2))
Centering and scaling data matrix
|
| | 0%
|
|==================================================================================| 100%
seurat_CRC2 <- RunPCA(seurat_CRC2, features = rownames(seurat_CRC2))
PC_ 1
Positive: C1QBP, HMGB2, CMBL, PPP1R1B, TYMS, SLC12A2, STMN1, KRTCAP3, CDCA7, FERMT1
CD24, TK1, PCLAF, CA2, IMPDH2, PLPP2, MLPH, EGFR, EPHB3, SOX9
RRM2, S100P, SFXN1, UBE2C, REG4, MKI67, RNF43, IL17RB, PBK, SMOC2
Negative: RPS4Y1, DPYSL3, SELENOM, ETS1, MAF, RORA, TRAC, ROBO1, GIMAP7, ANXA1
SPOCK2, CD79A, RUNX1T1, CD3E, CD2, TRBC2, IFITM1, DEPP1, THBS1, CD3G
IL7R, FYB1, VCAN, IGFBP7, CTLA4, GPR183, CD3D, TNFAIP3, GZMK, CXCR4
PC_ 2
Positive: APOE, CEACAM5, C1QB, CTSB, CCL4, CEACAM6, SERPINA1, C1QA, C1QC, CYBB
RNASE1, MS4A7, FABP2, CD14, CD83, CEACAM1, CCL5, GZMA, TNFSF13B, CD163
MYH14, NKG7, CD8A, FYB1, IL1B, COL17A1, CDK6, RETNLB, RHOV, TBC1D4
Negative: MEIS2, NOVA1, ALDH1B1, CLU, AQP1, AGTR1, ID2, GNA11, FZD7, SLC6A8
IMPDH2, CES1, PDE4C, SEC11C, ADH1C, LYVE1, MAOB, EGFR, WFDC2, RGMB
CTSG, KIT, ANK2, PRDX4, ROBO2, EBPL, TKT, SELENOK, DERL3, PRPH
PC_ 3
Positive: ALDH1B1, GNA11, C1QC, RNASE1, MEIS2, CKAP4, C1QB, CD163, CTSB, C1QA
PROX1, RGMB, TUBA1A, CD14, CES1, AGTR1, SLC6A8, MAOB, CPE, MS4A7
KIT, FZD7, THBS1, WFDC2, ETV1, MS4A2, CPA3, RUNX1T1, TIMP3, PLXND1
Negative: PAX5, CXCR5, FCRL1, MS4A1, SPIB, TCL1A, BANK1, CHI3L2, FCER2, FCRLA
CCR7, IRF8, SELL, CD40LG, CD83, TRAT1, VPREB3, CD79B, SMIM14, LTB
IER5, COL19A1, GZMK, MKI67, LGALS2, CXCR4, BATF, PTTG1, DNASE1L3, CD6
PC_ 4
Positive: INSM1, ASCL2, TIMP3, IGFBP7, EPHB3, GATA2, THBS1, TUBB, VCAN, MUC12
PRDX4, LEF1, CDCA7, ETV1, CPE, PCLAF, FRZB, REG4, HES6, MAOB
RRM2, SCG2, LGR5, IFITM1, FERMT1, CD24, CA2, ROBO1, DPYSL3, AFAP1L2
Negative: CES2, SDCBP2, SMIM14, COL17A1, SLPI, CDHR5, HHLA2, SLC6A8, DMBT1, HDC
FABP2, ANXA13, TFF1, AREG, GPRC5C, PDE4C, BCAS1, RHOV, SULT1B1, GNA11
RNASE1, GALNT8, CEACAM6, MYH14, ODF2L, MEIS2, CFTR, UGT2B17, PDZK1IP1, DUOX2
PC_ 5
Positive: PRDX4, LGALS2, CTSB, RNASE1, ID2, CA2, GNLY, CCL4, IL1B, NKG7
CD163, CES2, SLPI, KLRC2, C1QC, SERPINA1, TUBA1A, AKR7A3, PLCE1, AQP1
GPRIN3, GNA11, CDHR5, CXCL3, GZMA, EBPL, C1QA, CCL5, MLPH, PSTPIP2
Negative: FOXA3, MAOB, LEFTY1, PROX1, MUC12, RAB26, CDK6, HEPACAM2, RETNLB, KLK1
WFDC2, SMIM14, OLFM4, ATOH1, HES6, RGMB, INSM1, CEACAM6, CRYBA2, CHGB
SCNN1A, MB, CEACAM1, ASCL2, MS4A8, FCER2, RFX6, CHGA, CEACAM5, TNFRSF25
seurat_CRC2 <- FindNeighbors(seurat_CRC2, reduction = "pca", dims = 1:10)
Computing nearest neighbor graph
Computing SNN
seurat_CRC2 <- FindClusters(seurat_CRC2, resolution = 0.1, cluster.name = "Niches")
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
Number of nodes: 73985
Number of edges: 1833699
Running Louvain algorithm...
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.9640
Number of communities: 9
Elapsed time: 10 seconds
Lets visualise the detected “niches”. We can see that we have
achieved a coarse partioning of the cells into crypt top, mid-crypt and
crypt-base regions, as well as segmenting out follicles and sub-mucosal
stroma.
How would you tweak the above approach to generate more or
less granular niches?
ImageDimPlot(seurat_CRC2, group.by = "Niches")
Warning: No FOV associated with assay 'NEIGHBOURHOOD100', using global default FOV


We can tabulate our detected niches with predicted cell type labels
(or clusters) to visualise enrichment of different cell types across
spatial niches.
For example, as could be expected, T-Cells and B-Cells enrich in
Niche 2 (follicular).




saveRDS(seurat_CRC2, "seurat_CRC2_annot.RDS")
change the color
ImageDimPlot(seurat_CRC2, cols = cell_colours, group.by = "predicted.id")
Warning: No FOV associated with assay 'NEIGHBOURHOOD100', using global default FOV


merged <- readRDS("CRC_merge")
Warning: cannot open compressed file 'CRC_merge', probable reason 'No such file or directory'Error in gzfile(file, "rb") : cannot open the connection
LS0tCnRpdGxlOiAiSHVtYW4gQ29sb24gWGVuaXVtIGluIHNpdHUgU1QgRGF0YXNldCwgTnVjbGVpIFNlZ21lbnRhdGlvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCkZpcnN0IHdlIG5lZWQgdG8gc2V0IHVwIHRoZSBlbnZpcm9ubWVudCBhbmQgbG9hZCB0aGUgcGFja2FnZXMgd2Ugd2lsbCB1c2UgZm9yIHRoaXMgd29ya3Nob3AuIAoKKmxpYnJhcnkoU2V1cmF0KSo6IExvYWRzIHRoZSBTZXVyYXQgcGFja2FnZSwgd2hpY2ggaXMgYSBjb21wcmVoZW5zaXZlIHRvb2xraXQgZm9yIHNpbmdsZS1jZWxsIFJOQSBzZXF1ZW5jaW5nIGFuZCBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBkYXRhIGFuYWx5c2lzLiBJdCBwcm92aWRlcyBhIHdpZGUgcmFuZ2Ugb2YgZnVuY3Rpb25zIGZvciBkYXRhIHByZXByb2Nlc3NpbmcsIG5vcm1hbGl6YXRpb24sIGNsdXN0ZXJpbmcsIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiwgYW5kIHZpc3VhbGl6YXRpb24uIEV4cGxvcmUgZG9jdW1lbnRhdGlvbiBoZXJlOiBodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0LwoKKmxpYnJhcnkoZ2dwbG90MikqOiBMb2FkcyB0aGUgZ2dwbG90MiBwYWNrYWdlLCBhIHBvd2VyZnVsIGFuZCBmbGV4aWJsZSBzeXN0ZW0gZm9yIGNyZWF0aW5nIHN0YXRpYyB2aXN1YWxpemF0aW9ucyBpbiBSLiBFeHBsb3JlIGRvY3VtZW50YXRpb24gaGVyZTogaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvCgoqbGlicmFyeShzY0N1c3RvbWl6ZSkqOiBMb2FkcyB0aGUgc2NDdXN0b21pemUgcGFja2FnZSwgd2hpY2ggcHJvdmlkZXMgY3VzdG9tIGZ1bmN0aW9ucyBhbmQgdGhlbWVzIHRvIGVuaGFuY2UgdGhlIHZpc3VhbGl6YXRpb24gYW5kIGFuYWx5c2lzIGNhcGFiaWxpdGllcyBvZiBzaW5nbGUtY2VsbCBhbmQgc3BhdGlhbCB0cmFuc2NyaXB0b21pY3MgZGF0YSwgb2Z0ZW4gaW4gY29uanVuY3Rpb24gd2l0aCBTZXVyYXQuIEV4cGxvcmUgZG9jdW1lbnRhdGlvbiBoZXJlOiBodHRwczovL3NhbXVlbC1tYXJzaC5naXRodWIuaW8vc2NDdXN0b21pemUvCgoqbGlicmFyeShyZWFkcikqOiBMb2FkcyByZWFkciBwYWNrYWdlIGZvciBmYXN0IGFuZCBmcmllbmRseSByZWFkaW5nIG9mIHJlY3Rhbmd1bGFyIGRhdGEsIHN1Y2ggYXMgQ1NWIGZpbGVzLCBpbnRvIFIuCgoqbGlicmFyeShwaGVhdG1hcCkqOiBMb2FkcyBwaGVhdG1hcCBwYWNrYWdlLCB3aGljaCBpcyBmb3IgY3JlYXRpbmcgcHJldHR5IGhlYXRtYXBzLCBvZmZlcmluZyBiZXR0ZXIgY29udHJvbCBvdmVyIGhlYXRtYXAgY3VzdG9taXphdGlvbiBjb21wYXJlZCB0byBiYXNlIFIuCgoqbGlicmFyeShtYXRyaXhTdGF0cykqOiBtYXRyaXhTdGF0cyBwcm92aWRlcyBoaWdobHkgb3B0aW1pemVkIGZ1bmN0aW9ucyBmb3IgbWF0cml4IG9wZXJhdGlvbnMsIHBhcnRpY3VsYXJseSB1c2VmdWwgZm9yIGNvbXB1dGluZyByb3cgYW5kIGNvbHVtbiBzdW1tYXJpZXMuIAoKKmxpYnJhcnkoc3BkZXApKjogc3BkZXAgc3RhbmRzIGZvciBTcGF0aWFsIERlcGVuZGVuY2UgYW5kIFNwYXRpYWwgQXV0b2NvcnJlbGF0aW9uLCBhbmQgaXQgcHJvdmlkZXMgZnVuY3Rpb25zIGZvciBzcGF0aWFsIGRhdGEgYW5hbHlzaXMsIGluY2x1ZGluZyBzcGF0aWFsIHdlaWdodHMgZ2VuZXJhdGlvbiwgc3BhdGlhbCBhdXRvY29ycmVsYXRpb24gc3RhdGlzdGljcywgYW5kIHNwYXRpYWwgcmVncmVzc2lvbi4KCipsaWJyYXJ5KGdlb2pzb25SKSogVGhlIGdlb2pzb25SIGxpYnJhcnkgaXMgdXNlZCBmb3IgaGFuZGxpbmcgR2VvSlNPTiBkYXRhIGluIFIuIEdlb0pTT04gaXMgYSBmb3JtYXQgZm9yIGVuY29kaW5nIGEgdmFyaWV0eSBvZiBnZW9ncmFwaGljIGRhdGEgc3RydWN0dXJlcyB1c2luZyBKYXZhU2NyaXB0IE9iamVjdCBOb3RhdGlvbiAoSlNPTikuIEl0IGlzIHNvbWV0aW1lcyB1c2VkIGFzIGEgZm9ybWF0IGZvciBzdG9yaW5nIGNlbGwgc2VnbWVudGF0aW9uIGJvdW5kYXJpZXMuCgpgYGB7cn0KbGlicmFyeShTZXVyYXQpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzY0N1c3RvbWl6ZSkKbGlicmFyeShyZWFkcikKbGlicmFyeShwaGVhdG1hcCkKbGlicmFyeShtYXRyaXhTdGF0cykKbGlicmFyeShzcGRlcCkKbGlicmFyeShnZW9qc29uUikKYGBgClNldHMgdGhlIHBhdGggdG8gdGhlIGRpcmVjdG9yeSBjb250YWluaW5nIHRoZSBYZW5pdW0gb3V0cHV0IGRhdGEgLSB0aGlzIGlzIHRoZSBkaXJlY3Rvcnkgd2hlcmUgYWxsIG9mIHRoZSBvdXRwdXRzIGFyZSBzdG9yZWQuCmBgYHtyfQpkYXRhX2RpciA8LSAiL3Byb2plY3Qvc2hhcmVkL3NwYXRpYWxfZGF0YV9jYW1wL2RhdGFzZXRzL0RBVEFTRVQyL1hFTklVTV9DT0xPUkVDVEFMX0NBTkNFUi8iCmBgYAoKKlJlYWRYZW5pdW0qIHJlYWRzIFhlbml1bSBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBkYXRhIGZyb20gYSBzcGVjaWZpZWQgZGlyZWN0b3J5IHVzaW5nIGEgU2V1cmF0IHdyYXBwZXIgZnVuY3Rpb24gdGhhdCBzdXBwb3J0cyB0aGlzIGRhdGEgZm9ybWF0LiBYZW5pdW0gZGF0YSB0eXBpY2FsbHkgaW5jbHVkZXMgZXhwcmVzc2lvbiBtYXRyaWNlcyBhbmQgc3BhdGlhbCBjb29yZGluYXRlcywgYWxvbmcgd2l0aCBvdGhlciAgaW5mb3JtYXRpb24gYWJvdXQgY2VsbCBjZW50cm9pZHMgYW5kIHNlZ21lbnRhdGlvbnMgYW5kIGNvb3JkaW5hdGVzIG9mIGluZGl2aWR1YWwgdHJhbnNjcmlwdHMuIAoKKmRhdGFfZGlyKjogVGhlIHBhdGggdG8gdGhlIGRpcmVjdG9yeSBjb250YWluaW5nIHRoZSBYZW5pdW0gZGF0YSwgc2V0IGluIHRoZSBwcmV2aW91cyBzdGVwLgoqb3V0cyA9IGMoIm1hdHJpeCIsICJtaWNyb25zIikqOiBTcGVjaWZpZXMgdGhlIG91dHB1dHMgdG8gcmVhZCBmcm9tIHRoZSBkYXRhIGRpcmVjdG9yeS4gbWF0cml4IHJlZmVycyB0byBzdW1tYXJpc2VkIGNlbGwgYnkgZ2VuZSBtYXRyaXggYW5kIG1pY3JvbnMgcmVmZXJzIHRvIGluZGl2aWR1YWwgdHJhbnNjcmlwdCBjb29yZGluYXRlcy4KCip0eXBlID0gYygiY2VudHJvaWRzIiwgInNlZ21lbnRhdGlvbnMiKSo6IEluZGljYXRlcyB0aGUgdHlwZXMgb2Ygc3BhdGlhbCBpbmZvcm1hdGlvbiB0byBpbmNsdWRlIC0gaGVyZSwgd2UgYXJlIHJlYWRpbmcgaWIgYm90aCBjZWxsIGNlbnRyb2lkIGNvb3JkaW5hdGVzIGFuZCBjZWxsIGJvdW5kYXJ5IHNlZ21lbnRhdGlvbnMuCgoKYGBge3J9CmRhdGEgPC0gUmVhZFhlbml1bShkYXRhX2Rpciwgb3V0cyA9IGMoIm1hdHJpeCIsICJtaWNyb25zIiksIHR5cGU9YygiY2VudHJvaWRzIiwgInNlZ21lbnRhdGlvbnMiKSkKYGBgClRoaXMgcHJvdmlkZXMgdXMgYSBsaXN0IG9mIGRhdGE6CmBgYHtyfQpuYW1lcyhkYXRhKQpgYGAKTWF0cml4IGlzIGZ1cnRoZXIgc3BsaXQgaW50byBnZW5lIGV4cHJlc3Npb24gbWF0cml4IGFuZCB2YXJpb3VzIGNvbnRyb2wgcHJvYmVzIGFuZCBjb2Rld29yZHMuIERpZmZlcmVudCBwbGF0Zm9ybXMgYW5kIHBsYXRmb3JtIHZlcnNpb25zIGluY2x1ZGUgZGlmZmVyZW50IGNvbnRyb2wgcHJvYmVzLiBBcyB0aGlzIHdpbGwgdmFyeSwgaXQncyBpbXBvcnRhbnQgdG8gY2hlY2sgYW5kIHVuZGVyc3RhbmQgd2hhdCB0aGUgc3BlY2lmaWMgY29udHJvbHMgaW4geW91ciBvd24gZGF0YSBhcmUuICAKCkhlcmUsIG5lZ2F0aXZlIGNvbnRyb2wgcHJvYmVzIGFyZSBwcm9iZXMgdGhhdCBhcmUgYWRkZWQgdG8gdGhlIHJlYWN0aW9uIGJ1dCB0YXJnZXQgbm9uLWJpb2xvZ2ljYWwgc2VxdWVuY2VzIGFuZCBzaG91bGQgbm90IGJpbmQgYW55IHRpc3N1ZSBSTkEuIE5lZ2F0aXZlIGNvbnRyb2wgY29kZXdvcmRzIGFyZSB2YWxpZCBjb2Rld29yZHMsIGJ1dCBubyBwcm9iZXMgd2l0aCB0aGF0IGNvZGV3b3JkIGFkZGVkIHRvIHRoZSByZWFjdGlvbi4gVGhpcyBlZmZlY3RpdmVseSB0ZWxscyB1cyBob3cgZ29vZCB0aGUgdHJhbnNjcmlwdCBjYWxsaW5nIGFsZ29yaXRobSBpcy4KCmBgYHtyfQpuYW1lcyhkYXRhJG1hdHJpeCkKYGBgClJlYWQgaW4gYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgY2VsbHMgLSB0aGlzIGdpdmVzIHVzIHByZS1jYWxjdWxhdGVkIGluZm9ybWF0aW9uLCBmb3IgZXhhbXBsZSBzZWdtZW50ZWQgY2VsbCBvciBudWNsZXVzIHNpemUgZm9yIGVhY2ggY2VsbC4KYGBge3J9CmNlbGxfbWV0YV9kYXRhIDwtIHJlYWQuY3N2KGZpbGUucGF0aChkYXRhX2RpciwgImNlbGxzLmNzdi5neiIpKQpyb3duYW1lcyhjZWxsX21ldGFfZGF0YSkgPC0gY2VsbF9tZXRhX2RhdGEkY2VsbF9pZApoZWFkKGNlbGxfbWV0YV9kYXRhKQpgYGAKCldlIHdpbGwgc3RhcnQgYnkgY3JlYXRpbmcgYSBiYXNpYyBzZXVyYXQgb2JqZWN0IGZyb20gdGhlIGRhdGEuIAoKKkNyZWF0ZVNldXJhdE9iamVjdCogZnVuY3Rpb24gaW5pdGlhbGl6ZXMgYSBTZXVyYXQgb2JqZWN0IHVzaW5nIHRoZSBwcm92aWRlZCBnZW5lIGV4cHJlc3Npb24gbWF0cml4IGFuZCBvcHRpb25hbCBtZXRhZGF0YS4KCipjb3VudHMqOiBUaGUgZ2VuZSBleHByZXNzaW9uIG1hdHJpeCwgd2hpY2ggY29udGFpbnMgdGhlIHJhdyBjb3VudCBkYXRhIGZvciBlYWNoIGdlbmUgaW4gZWFjaCBjZWxsLgoqZGF0YSRtYXRyaXhbWyJHZW5lIEV4cHJlc3Npb24iXV0qOiBTcGVjaWZpZXMgdGhlIGdlbmUgZXhwcmVzc2lvbiBtYXRyaXggZXh0cmFjdGVkIGZyb20gdGhlIGxvYWRlZCBYZW5pdW0gZGF0YS4gSGVyZSwgd2UgbGVhdmUgb3V0IHRoZSBjb250cm9sIHByb2JlcyBmb3Igbm93LiAKCiphc3NheSo6IFRoZSBuYW1lIG9mIHRoZSBhc3NheSAtIHlvdSBjYW4gY2FsbCBpdCBhbnl0aGluZyB5b3UgbGlrZS4gSGVyZSwgd2UgZ28gd2l0aCAiWEVOSVVNIi4gCgoqbWV0YS5kYXRhKjogTWV0YWRhdGEgYXNzb2NpYXRlZCB3aXRoIHRoZSBjZWxscyBvciBzcG90cy4gSGVyZSwgd2UgYWRkIHRoZSBjZWxsIHN0YXRpc3RpY3Mgd2UgcmVhZCBpbiBlYXJsaWVyIGFzICpjZWxsX21ldGFfZGF0YSouCgpCeSBwcmludGluZyB0aGUgKnNldXJhdCogb2JqZWN0LCB3ZSBjYW4gc2VlIHRoYXQgd2UgcmVhZCBpbiB+IDMwLDAwMCBjZWxscyB3aXRoIG1lYXN1cmVzIGZvciAzMjUgZ2VuZXMKCmBgYHtyfQpzZXVyYXQgPC0gQ3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cyA9IGRhdGEkbWF0cml4W1siR2VuZSBFeHByZXNzaW9uIl1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJYRU5JVU0iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRhLmRhdGEgPSBjZWxsX21ldGFfZGF0YSkKc2V1cmF0CmBgYAoKQWRkaW5nIHNwYXRpYWwgY29vcmRpbmF0ZXMgdG8gYSBTZXVyYXQgb2JqZWN0IGFsbG93cyBmb3Igc3BhdGlhbGx5IHJlc29sdmVkIGFuYWx5c2lzIGFuZCB2aXN1YWxpemF0aW9uLiBUaGlzIHJlcXVpcmVzIGNyZWF0aW5nIG9iamVjdHMgZm9yIGNlbnRyb2lkcyBhbmQgc2VnbWVudGF0aW9ucyB3ZSByZWFkIGluIGVhcmxpZXIsIGFuZCB0aGVuIGludGVncmF0aW5nIHRoZXNlIHdpdGggdGhlIG1haW4gU2V1cmF0IG9iamVjdC4KCipDcmVhdGVGT1YqOiBUaGlzIGZ1bmN0aW9uIGNyZWF0ZXMgYSBmaWVsZCBvZiB2aWV3IChGT1YpIG9iamVjdCB0aGF0IGluY2x1ZGVzIHNwYXRpYWwgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGNlbnRyb2lkcywgc2VnbWVudGF0aW9ucywgYW5kIG1vbGVjdWxlIGNvb3JkaW5hdGVzLiBBbiBGT1YgY2FuIGJlIHRoZSBlbnRpcmUgc2xpZGUsIG9yIGEgc2VsZWN0ZWQgcmVnaW9uIHdpdGhpbiBhIHNsaWRlIC0gaS5lLiBpdCBkb2VzIG5vdCBuZWVkIHRvIGhhdmUgZW50cmllcyBmb3IgYWxsIHRoZSBjZWxscyBpbiB0aGUgc2V1cmF0IG9iamVjdC4KCipjb29yZHMqOiBBIGxpc3QgY29udGFpbmluZyB0aGUgY2VudHJvaWRzIGFuZC9vciBzZWdtZW50YXRpb24gZGF0YS4gRm9yIGxhcmdlciBkYXRhc2V0cywgaXQgY2FuIGJlIHF1aWNrZXIgdG8gb25seSBsb2FkIGNlbnRyb2lkcywgYXMgdGhpcyBtaW5pbWlzZXMgdGhlIGFtb3VudCBvZiBkYXRhIHBvaW50cy4gCgoqY2VudHJvaWRzID0gQ3JlYXRlQ2VudHJvaWRzKGRhdGEkY2VudHJvaWRzKSo6IENyZWF0ZXMgYSBjZW50cm9pZHMgb2JqZWN0IGZyb20gdGhlIGNlbnRyb2lkIGRhdGEgaW4gdGhlIFhlbml1bSBkYXRhc2V0Lgoqc2VnbWVudGF0aW9uID0gQ3JlYXRlU2VnbWVudGF0aW9uKGRhdGEkc2VnbWVudGF0aW9ucykqOiBDcmVhdGVzIGEgc2VnbWVudGF0aW9uIG9iamVjdCBmcm9tIHRoZSBzZWdtZW50YXRpb24gZGF0YSBpbiB0aGUgWGVuaXVtIGRhdGFzZXQuCgoqdHlwZSA9IGMoInNlZ21lbnRhdGlvbiIsICJjZW50cm9pZHMiKSo6IFNwZWNpZmllcyB0aGUgdHlwZXMgb2Ygc3BhdGlhbCBkYXRhIGJlaW5nIGluY2x1ZGVkLCB3aGljaCBhcmUgc2VnbWVudGF0aW9uIGFuZCBjZW50cm9pZCBkYXRhLgoKKm1vbGVjdWxlcyA9IGRhdGEkbWljcm9ucyo6IFRoZSBzcGF0aWFsIGNvb3JkaW5hdGVzIG9mIGluZGl2aWR1YWwgdHJhbnNjcmlwdHMvbW9sZWN1bGVzIGluIHRoZSBkYXRhLiBUaGlzIGlzIG9wdGlvbmFsIC0gZm9yIGxhcmdlciBkYXRhc2V0cywgc2tpcHBpbmcgdHJhbnNjcmlwdCBjb29yZGluYXRlcyBjYW4gYmUgYSBnb29kIGlkZWEuCgoqc2V1cmF0W1siQ09MT04iXV0gPC0gY29vcmRzKjogQWRkcyB0aGUgY3JlYXRlZCBGT1Ygb2JqZWN0IHRvIHRoZSBTZXVyYXQgb2JqZWN0IHVuZGVyIHRoZSBuZXcgRk9WIG5hbWUgIkNPTE9OIi4gVGhpcyBjYW4gYmUgbmFtZWQgKGFsbW9zdCkgYW55dGhpbmcgLSBidXQsIGF2b2lkIHVzaW5nIHVuZGVyc2NvcmVzIGFzIHRoaXMgY2FuIGNyZWF0ZSBzb21lIHVuZXhwZWN0ZWQgYmVoYXZpb3VycyBsYXRlci4KClRJUDogKkxvYWRYZW5pdW0oKSogaXMgYSB3cmFwcGVyIHRoYXQgd291bGQgbG9hZCBpbiBib3RoIGNlbGwgY291bnRzIG1hdHJpeCBhbmQgc3BhdGlhbCBjb29yZGluYXRlcyBpbiBvbmUgZnVuY3Rpb24sIHNpbXBsaWZ5aW5nIHRoZXNlIHN0ZXBzLiBIb3dldmVyLCAqaW4gc2l0dSogcGxhdGZvcm1zIGFyZSBldm9sdmluZyBhdCBhIHZlcnkgZmFzdCByYXRlIGFuZCB0aGVyZSBhcmUgY29uc3RhbnQgY2hhbmdlcyBvbiBob3cgdGhlIGRhdGEgaXMgc3RvcmVkLCBpbiBwYXJ0aWN1bGFyIGZvciBmaWxlIGZvcm1hdHMgZm9yIGNlbGwgc2VnbWVudGF0aW9uIGFuZCBjb29yZGluYXRlcy4gSGVyZSwgd2UgaGF2ZSBicm9rZW4gZG93biB0aGUgc3RlcHMgdG8gc2hvdyBob3cgdG8gYXNzZW1ibGUgYW4gaW4gc2l0dSBzZXVyYXQgb2JqZWN0IGZyb20gdGhlIGtleSBjb21wb25lbnRzLCBpbiBjYXNlIHRoZSBwbGF0Zm9ybSBzcGVjaWZpYyByZWFkZXJzIGRvbid0IHdvcmsgZm9yIHlvdXIgc3BlY2lmaWMgZGF0YS4KYGBge3J9CmNvb3JkcyA8LSBDcmVhdGVGT1YoY29vcmRzID0gbGlzdChjZW50cm9pZHMgPSBDcmVhdGVDZW50cm9pZHMoZGF0YSRjZW50cm9pZHMpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZ21lbnRhdGlvbiA9IENyZWF0ZVNlZ21lbnRhdGlvbihkYXRhJHNlZ21lbnRhdGlvbnMpKSwKICAgICAgICAgICAgICAgICAgICB0eXBlID0gYygic2VnbWVudGF0aW9uIiwgImNlbnRyb2lkcyIpLAogICAgICAgICAgICAgICAgICAgIG1vbGVjdWxlcyA9IGRhdGEkbWljcm9ucywKICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJYRU5JVU0iKQpzZXVyYXRbWyJDT0xPTkMyIl1dIDwtIGNvb3JkcyAgCmBgYAoKSW5zcGVjdCB0aGUgb2JqZWN0IC0gbm93LCB5b3UgY2FuIHNlZSB3ZSBoYXZlIGFkZGVkIGEgc3BhdGlhbCBmaWVsZCBvZiB2aWV3OgoKVG8gc3Vic2V0IHRoZSBvYmplY3QKYGBge3J9CmNyb3BwZWQgPC0gQ3JvcChzZXVyYXRbWyJDT0xPTkMyIl1dLCB4ID0gYygxMDAwLCAzMDAwKSwgeSA9IGMoMzAwMCwgNjAwMCksIGNvb3JkcyA9ICJwbG90IikKc2V1cmF0W1siQ1JDMiJdXSA8LSBjcm9wcGVkCkltYWdlRGltUGxvdChzZXVyYXQsIGZvdj0gIkNSQzIiLGF4ZXMgPSBUKQpgYGAKQWRkaW5nIGNvbnRyb2wgcHJvYmVzIGFuZCBjb2Rld29yZHMgYXMgc2VwYXJhdGUgYXNzYXlzIGluIHRoZSBTZXVyYXQgb2JqZWN0IGFsbG93cyBmb3IgdGhlIHRyYWNraW5nIGFuZCBhbmFseXNpcyBvZiB0ZWNobmljYWwgYXJ0aWZhY3RzIGFuZCBub2lzZSB3aXRoaW4geW91ciBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBkYXRhLCB3aGlsZSBrZWVwaW5nIHRoZXNlIG91dHB1dHMgc2VwYXJhdGUgZnJvbSB0aGUgbWFpbiBiaW9sb2dpY2FsIGdlbmUgZXhwcmVzc2lvbiB2YWx1ZXMuCgoKKipVbmFzc2lnbmVkIGNvZGV3b3JkcyoqIGFyZSB1bnVzZWQgY29kZXdvcmRzLiBUaGVyZSBpcyBubyBwcm9iZSBpbiBhIHBhcnRpY3VsYXIgZ2VuZSBwYW5lbCB0aGF0IHdpbGwgZ2VuZXJhdGUgdGhlIGNvZGV3b3JkLgoKKipOZWdhdGl2ZSBjb250cm9sIHByb2JlcyoqIGFyZSBwcm9iZXMgdGhhdCBleGlzdCBpbiB0aGUgcGFuZWxzIGJ1dCB0YXJnZXQgbm9uLWJpb2xvZ2ljYWwgc2VxdWVuY2VzLiBUaGV5IGNhbiBiZSB1c2VkIHRvIGFzc2VzcyB0aGUgc3BlY2lmaWNpdHkgb2YgdGhlIGFzc2F5LgoKKipOZWdhdGl2ZSBjb250cm9sIGNvZGV3b3JkcyoqIGFyZSBjb2Rld29yZHMgaW4gdGhlIGNvZGVib29rIHRoYXQgZG8gbm90IGhhdmUgYW55IHByb2JlcyBtYXRjaGluZyB0aGF0IGNvZGUuIFRoZXkgYXJlIGNob3NlbiB0byBtZWV0IHRoZSBzYW1lIHJlcXVpcmVtZW50cyBhcyByZWd1bGFyIGNvZGV3b3JkcyBhbmQgY2FuIGJlIHVzZWQgdG8gYXNzZXNzIHRoZSBzcGVjaWZpY2l0eSBvZiB0aGUgZGVjb2RpbmcgYWxnb3JpdGhtLgoKCmBgYHtyfQpzZXVyYXRbWyJOZWdhdGl2ZS5Db250cm9sLkNvZGV3b3JkIl1dIDwtIENyZWF0ZUFzc2F5T2JqZWN0KGNvdW50cyA9IGRhdGEkbWF0cml4W1siTmVnYXRpdmUgQ29udHJvbCBDb2Rld29yZCJdXSkKc2V1cmF0W1siTmVnYXRpdmUuQ29udHJvbC5Qcm9iZSJdXSA8LSBDcmVhdGVBc3NheU9iamVjdChjb3VudHMgPSBkYXRhJG1hdHJpeFtbIk5lZ2F0aXZlIENvbnRyb2wgUHJvYmUiXV0pCnNldXJhdFtbIlVuYXNzaWduZWQuQ29kZXdvcmQiXV0gPC0gQ3JlYXRlQXNzYXlPYmplY3QoY291bnRzID0gZGF0YSRtYXRyaXhbWyJVbmFzc2lnbmVkIENvZGV3b3JkIl1dKQpgYGAKCnN1YnNldCBhbiBvYmplY3QKYGBge3J9CiMgeCA9IGMoMTAwMCwgMzAwMCksIHkgPSBjKDMwMDAsIDYwMDApCnNldXJhdF9DUkMyIDwtIHNldXJhdFssIHNldXJhdCR4X2NlbnRyb2lkID49IDEwMDAgJiBzZXVyYXQkeF9jZW50cm9pZCA8PSAzMDAwICYgc2V1cmF0JHlfY2VudHJvaWQgPj0gMzAwMCAmIHNldXJhdCR5X2NlbnRyb2lkIDw9IDYwMDBdCnNldXJhdF9DUkMyICNyZWFkIHRoZSBvYmplY3QKc2V1cmF0ICNyZWFkIHRoZSBvYmplY3QKc2F2ZVJEUyhzZXVyYXRfQ1JDMiwgZmlsZT0iQ1JDMl9zdWJzZXQuUkRTIikKYGBgCkxldCdzIHN0YXJ0IHdpdGggc29tZSBiYXNpYyBRQyBhbmQgdmlzdWFsaXNhdGlvbiBvZiB0aGUgZGF0YS4gCgpJbiBTZXVyYXQsICppbiBzaXR1KiBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBjb3VudGVycGFydCBmdW5jdGlvbnMgdG8gKidTcGF0aWFsRGltUGxvdCcqIGFuZCAqJ1NwYXRpYWxGZWF0dXJlUGxvdCcqIHdlIGNvdmVyZWQgeWVzdGVyZGF5IGFyZSBjYWxsZWQgKidJbWFnZUZlYXR1cmVQbG90JyogYW5kIConSW1hZ2VEaW1QbG90JyouIFRoZXNlIGhhdmUgYWRkaXRpb25hbCBmdW5jdGlvbmFsaXR5IHRvIHBsb3QgY2VsbCBzZWdtZW50YXRpb25zIGFuZCBpbmRpdmlkdWFsIHRyYW5zY3JpcHQgY29vcmRpbmF0ZXMsIGJ1dCBvdGhlcndpc2UgZnVuY3Rpb24gZXhhY3RseSB0aGUgc2FtZSBhcyB0aGUgc2VxdWVuY2luZyBiYXNlZCBTVCBjb3VudGVycGFydHMuIAoKRmlyc3QsIGxldHMgdmlzdWFsaXNlIHRoZSB0b3RhbCB0cmFuc2NyaXB0cyBkZXRlY3RlZCBwZXIgY2VsbC4KCkFzIGluIHNjUk5BLVNlcSBkYXRhLCB0aGlzIGlzIHRoZSBtb3N0IGJhc2ljIG1lYXN1cmUgb2Ygb3ZlcmFsbCBzaWduYWwgYW5kIGhvdyB3ZWxsIHRoZSBkYXRhIGxvb2tzLiAKClVubGlrZSBpbiBzY1JOQS1TZXEgZGF0YSBvciB1bmJpYXNlZCBzZXF1ZW5jaW5nLWJhc2VkIFNULCB0aGVzZSBtZWFzdXJlcyBhcmUgYWxzbyB2ZXJ5IGhlYXZpbHkgZGVwZW5kZW50IG5vdCBvbmx5IG9uIHRoZSB0b3RhbCBSTkEgcXVhbnRpdHkgb2YgZWFjaCBjZWxsIGFuZCB0aXNzdWUgcXVhbGl0eSwgYnV0IGFsc28gb24gdGhlIHRhcmdldCBwYW5lbCB1c2VkIGZvciB0aGUgZXhwZXJpbWVudC4gVW5kZXItcmVwcmVzZW50ZWQgY2VsbCB0eXBlcyB3aWxsIG5hdHVyYWxseSB5aWVsZCBmZXdlciB0cmFuc2NyaXB0cy4KRmluYWxseSwgdGhlIHF1YWxpdHkgb2YgY2VsbCBzZWdtZW50YXRpb24gYWxzbyBwbGF5cyBhIHJvbGUuCgpJbiB0aGlzIGNhc2UsIHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgYXJlYXMgd2l0aCBoaWdoZXIgYW5kIGxvd2VyIHRvdGFsIHRyYW5zY3JpcHRzIGRldGVjdGVkLiAKClVuZGVyc3RhbmRpbmcgeW91ciB0aXNzdWUgYW5kIHRhcmdldCBwYW5lbCBoZXJlIGlzIGltcG9ydGFudCB0byBkZWxpbmVhdGUgd2hlcmUgdGhlc2UgZGlmZmVyZW5jZXMgYXJlIGJpb2xvZ2ljYWwgYW5kIHdoZXJlIHRoZXkgbWF5IGJlIHRlY2huaWNhbC4KCmBgYHtyfQojeCA9IGMoMTAwMCwgMzAwMCksIHkgPSBjKDMwMDAsIDYwMDApLApgYGAKU2ltaWxhcmx5LCB3ZSBjYW4gdmlzdWFsaXNlIHRoZSB0b3RhbCBudW1iZXIgb2YgZ2VuZSBkZXRlY3RlZCBwZXIgY2VsbC4gWW91IGNhbiBzZWUgdGhhdCB0aGlzIGlzIGEgYml0IGxlc3MgdmFyaWFibGUgYWNyb3NzIHRpc3N1ZS4gCgpUaGlzIGNhbiBhbHNvIHN1Z2dlc3QgdGhhdCB0aGVyZSBjZWxscyBhdCB0aGUgdG9wIG9mIHRoZSBlcGl0aGVsaWFsIGNyeXB0cyBpbiB0aGlzIHNhbXBsZSB3aXRoIGdlbmVzIGRldGVjdGVkIGF0IGhpZ2ggY29weSBudW1iZXIgdGhhbiB0aGUgcmVzdCBvZiB0aGUgdGlzc3VlLgoKYGBge3J9CkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJuRmVhdHVyZV9YRU5JVU0iLCBheGVzID0gVCkgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCmBgYAoKVGhpcyBjb2RlIGV4YW1pbmVzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIG51bWJlciBvZiBmZWF0dXJlcyAoZ2VuZXMpIGRldGVjdGVkIHBlciBjZWxsIGluIHRoZSBTZXVyYXQgb2JqZWN0IHVzaW5nIGEgZGVuc2l0eSBwbG90IGFuZCBjYWxjdWxhdGVzIHNwZWNpZmljIHF1YW50aWxlcyBvZiB0aGlzIGRpc3RyaWJ1dGlvbi4gVGhpcyBpcyBpbXBvcnRhbnQgZm9yIHVuZGVyc3RhbmRpbmcgdGhlIHZhcmlhYmlsaXR5IGFuZCBkaXN0cmlidXRpb24gb2YgZGV0ZWN0ZWQgZmVhdHVyZXMsIHdoaWNoIGNhbiBoZWxwIGlkZW50aWZ5IHBvdGVudGlhbCBpc3N1ZXMgc3VjaCBhcyBsb3ctcXVhbGl0eSBjZWxscyBhbmQgZGV0ZXJtaW5lIGFueSBmaWx0ZXJpbmcgdGhyZXNob2xkcyB0aGF0IG1heSBuZWVkIHRvIGJlIGFwcGxpZWQuCgpJZiB5b3UncmUgY29taW5nIGZyb20gc2NSTkEtU2VxIHdvcmssIHRoZXNlIGxvdyBudW1iZXJzIHByb2JhYmx5IGxvb2sgdmVyeSBhbGFybWluZy4gSG93IGNhbiB5b3UgcG9zc2libHkgd29yayB3aXRoIDMxIG1lZGlhbiBnZW5lcyBwZXIgY2VsbD8KClVubGlrZSBzY1JOQS1TZXEgZGF0YSBhbmQgc2VxdWVuY2luZy1iYXNlZCBTVCwgYm90aCBnZW5lIGRyb3BvdXRzIGFuZCBub2lzZSBhcmUgbXVjaCwgbXVjaCBsb3dlciBpbiAqaW4gc2l0dSogU1QgZGF0YS4gCgpXZSBhcmUgYWxzbyB3b3JraW5nIHdpdGggMTAwLWZvbGQgZmV3ZXIgdGFyZ2V0dGVkIGdlbmVzLgoKCmBgYHtyfQpnZ3Bsb3Qoc2V1cmF0X0NSQzJbW11dLCBhZXMobkZlYXR1cmVfWEVOSVVNKSkgKyBnZW9tX2RlbnNpdHkoKQpxdWFudGlsZShzZXVyYXRfQ1JDMiRuRmVhdHVyZV9YRU5JVU0sIGMoMC4wMSwgMC4xLCAwLjUsIDAuOSwgMC45OSkpCmBgYApVc2luZyAqSW1hZ2VGZWF0dXJlUGxvdCogdG8gdmlzdWFsaXplIHRoZSBjZWxsIGFyZWEgaW4gc3BhdGlhbCB0cmFuc2NyaXB0b21pY3MgZGF0YSBhbGxvd3MgdXMgdG8gZXhhbWluZSB0aGUgc3BhdGlhbCBvcmdhbml6YXRpb24gYW5kIHBvdGVudGlhbCBoZXRlcm9nZW5laXR5IG9mIGNlbGwgc2l6ZXMgd2l0aGluIHlvdXIgdGlzc3VlIHNhbXBsZS4KCioqV2h5IGRvIHdlIGdldCBzdWNoIGEgZGlmZmVyZW5jZSBpbiBzcGF0aWFsIGRpc3RyaWJ1dGlvbiBvZiBjZWxsIHNpemVzPyoqCgpUaGlzIGNvdWxkIGJlIGR1ZSB0byBiaW9sb2dpY2FsIGRpZmZlcmVuY2VzIGJldHdlZW4gc21hbGwgYW5kIGxhcmdlIGNlbGxzIC0gZS5nLiBzbWFsbCBjZWxscyBsaWtlIFQtY2VsbHMuIAoKSG93ZXZlciwgaGVyZSB0aGUgc2lnbmFsIGNvcnJlbGF0ZXMgd2l0aCBhcmVhcyBvZiBsb3cgY2VsbHVsYXJpc2F0aW9uLiBUaGVyZWZvcmUsIGl0IGlzIGxpa2VseSB0aGlzIGlzIGFuIGFydGVmYWN0IG9mIG51Y2xlaSBleHBhbnNpb24gaW4gY2VsbCBzZWdtZW50YXRpb24uIAoKV2hhdCBpcyBOdWNsZWkgRXhwYW5zaW9uPwoKTnVjbGVpIGV4cGFuc2lvbiBpbiBjZWxsIHNlZ21lbnRhdGlvbiByZWZlcnMgdG8gdGhlIHByb2Nlc3Mgb2YgZW5sYXJnaW5nIHRoZSBzZWdtZW50ZWQgbnVjbGVpIHJlZ2lvbnMgdG8gYXBwcm94aW1hdGUgdGhlIGJvdW5kYXJpZXMgb2YgdGhlIGVudGlyZSBjZWxscy4gVGhpcyB0ZWNobmlxdWUgaXMgdXNlZCB0byBiZXR0ZXIgcmVwcmVzZW50IHRoZSBhY3R1YWwgY2VsbCBib3VuZGFyaWVzIHdoZW4gb25seSB0aGUgbnVjbGVpIGhhdmUgYmVlbiBleHBsaWNpdGx5IHNlZ21lbnRlZC93ZSBvbmx5IGhhdmUgREFQSSBhbmQgbm8gYWRkaXRpb25hbCBjZWxsIGJvdW5kYXJ5IHN0YWluaW5nLiBUaGUgcHJpbWFyeSBnb2FsIGlzIHRvIHByb3ZpZGUgYSBtb3JlIGFjY3VyYXRlIGVzdGltYXRpb24gb2YgdGhlIGNlbGx1bGFyIGFyZWEsIHdoaWNoIGlzIGNydWNpYWwgZm9yIHZhcmlvdXMgZG93bnN0cmVhbSBhbmFseXNlcyBpbiBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBhbmQgc2luZ2xlLWNlbGwgc3R1ZGllcy4gSW4gdGhpcyBjYXNlLCBudWNsZWkgZXhwYW5zaW9uIGlzIGNvbnN0cmFpbmVkIGVpdGhlciBieSBtYXhpbXVtIGRpc3RhbmNlIG9yIG90aGVyIG5lYXJieSBjZWxscyAtIHNvLCB3aGVyZSB0aGVyZSBhcmUgbm8gb3RoZXIgbmVhcmJ5IGNlbGxzIHRvICJidW1wIGludG8iLCB0aGUgZXhwYW5zaW9uIGdlbmVyYXRlcyBhcnRpZmljaWFsbHkgYmlnZ2VyIGNlbGxzLgoKCmBgYHtyfQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiY2VsbF9hcmVhIiwgYXhlcyA9IFQpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKV2UgY2FuIGZ1cnRoZXIgY2hlY2sgdGhhdCB0aGlzIGlzIGxpa2VseSB0aGUgY2FzZSBieSBwbG90dGluZyB0aGUgcmF0aW8gYmV0d2VlbiBudWNsZWkgYW5kIHRvdGFsIGNlbGwgYXJlYS4gV2UgY2FuIHNlZSB0aGF0IHRoZXJlIGlzIGEgdmVyeSBiaWcgZGVjcmVhc2UgaW4gcGVyY2VudGFnZSBvZiBjZWxsIGFyZWEgb2NjdXBpZWQgYnkgbnVjbGV1cyBpbiBhcmVhcyBvZiBsb3cgY2VsbCBkZW5zaXR5LgoKVGhlIGNlbGwtdG8tbnVjbGV1cyBhcmVhIHJhdGlvIGNhbiBhbHNvIHBvdGVudGlhbGx5IHByb3ZpZGUgaW5zaWdodHMgaW50byBjZWxsIG1vcnBob2xvZ3ksIGNlbGwgdHlwZSBhbmQgcG90ZW50aWFsIGNoYW5nZXMgaW4gY2VsbHVsYXIgc3RhdGVzIG9yIGNvbmRpdGlvbnMuIEZvciBleGFtcGxlLCBULUNlbGxzIGNhbiBvZnRlbiBiZSBxdWl0ZSB3ZWxsIGlkZW50aWZpZWQgYnkgdGhpcyB2YXJpYWJsZSBhbG9uZSwgYXMgdGhleSBoYXZlIGEgc21hbGwgY3l0b3BsYXNtIHZvbHVtZS4gIEhvd2V2ZXIsIHdpdGhvdXQgYSBjZWxsIGJvdW5kYXJ5IHN0YWluLCB0aGlzIG1ldHJpYyBtYWlubHkgY2FwdHVyZXMgc2VnbWVudGF0aW9uIGFydGVmYWN0cywgc28gYmUgY2FyZWZ1bCBhYm91dCBvdmVyLWludGVycHJldGF0aW9uIQoKYGBge3J9CnNldXJhdF9DUkMyJGNlbGxfbnVjbGV1c19yYXRpbyA8LSBzZXVyYXRfQ1JDMiRudWNsZXVzX2FyZWEgLyBzZXVyYXRfQ1JDMiRjZWxsX2FyZWEKSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMiwgImNlbGxfbnVjbGV1c19yYXRpbyIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKCklmIHdlIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiwgd2Ugc2VlIHRoYXQgd2UgaGF2ZSBhIGJpZyB0YWlsIGVuZCBvZiBvdmVybHkgbGFyZ2UgY2VsbHMuCgpgYGB7cn0KZ2dwbG90KHNldXJhdF9DUkMyW1tdXSwgYWVzKGNlbGxfYXJlYSkpICsgZ2VvbV9kZW5zaXR5KCkKYGBgCkluIHRoaXMgY2FzZSwgd2UgY2FuIHNlZSB0aGF0IGFzIGV4cGVjdGVkLCB0aGVyZSBpcyBnZW5lcmFsbHkgYSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGNlbGwgYXJlYSBhbmQgdHJhbnNjcmlwdCBkZXRlY3Rpb24gcmF0ZS4gCgpIb3dldmVyLCB3ZSBhbHNvIGhhdmUgYSBncm91cCBvZiBjZWxscyB3aGVyZSB0aGlzIGlzIG5vdCB0aGUgY2FzZSAtIHZlcnkgbGFyZ2UgY2VsbHMgYnV0IHJlbGF0aXZlbHkgZmV3IHRyYW5zY3JpcHRzLiBUaGVzZSBjZWxscyBhcmUgbWFpbmx5IHN1Ym11Y29zYWwgc3Ryb21hbCBjZWxscyB3aGljaCBhcmUgdmVyeSBwb29ybHkgY292ZXJlZCBieSB0aGUgcGFuZWwgMTB4IGhhdmUgdXNlZC4gCgoKYGBge3J9CmdncGxvdChzZXVyYXRfQ1JDMltbXV0sIGFlcyhuQ291bnRfWEVOSVVNLCBjZWxsX2FyZWEpKSArIGdlb21fcG9pbnQoKSAKYGBgCldlIGNhbiBjcmVhdGUgYSBmaWx0ZXIgdG8gcmVtb3ZlIHRoZSBvdmVybHkgbGFyZ2UgY2VsbHMgZnJvbSB0aGUgYW5hbHlzaXMuCgoqcXVhbnRpbGUoc2V1cmF0JGNlbGxfYXJlYSwgMC45OSkqOiBDYWxjdWxhdGVzIHRoZSA5OXRoIHBlcmNlbnRpbGUgb2YgdGhlIGNlbGxfYXJlYSB2YWx1ZXMgaW4gdGhlIFNldXJhdCBvYmplY3QuIFRoaXMgdmFsdWUgc2VydmVzIGFzIGEgdGhyZXNob2xkIHRvIGlkZW50aWZ5IHRoZSBsYXJnZXN0IDElIG9mIGNlbGxzIC0gYnV0IHdoYXQgaXMgYSBzZW5zaWJsZSB0aHJlc2hvbGQsIGlmIGFueSwgZGVwZW5kcyBvbiB5b3VyIHRpc3N1ZS4KCipzZXVyYXQkY2VsbF9hcmVhIDwgcXVhbnRpbGUoc2V1cmF0JGNlbGxfYXJlYSwgMC45OSkqOiBDb21wYXJlcyBlYWNoIGNlbGwncyBhcmVhIHRvIHRoZSA5OXRoIHBlcmNlbnRpbGUgdGhyZXNob2xkLiBUaGUgcmVzdWx0IGlzIGEgbG9naWNhbCB2ZWN0b3Igd2hlcmUgZWFjaCBlbGVtZW50IGlzIFRSVUUgaWYgdGhlIGNvcnJlc3BvbmRpbmcgY2VsbCdzIGFyZWEgaXMgbGVzcyB0aGFuIHRoZSA5OXRoIHBlcmNlbnRpbGUgYW5kIEZBTFNFIG90aGVyd2lzZS4KCgoqc2V1cmF0W1siU0laRV9GSUxURVJfTEFSR0UiXV0qOiBDcmVhdGVzIGEgbmV3IG1ldGFkYXRhIGZpZWxkIG5hbWVkIFNJWkVfRklMVEVSX0xBUkdFIGluIHRoZSBTZXVyYXQgb2JqZWN0LCBzdG9yaW5nIHRoZSBsb2dpY2FsIHZlY3Rvci4KCgpgYGB7cn0Kc2V1cmF0X0NSQzJbWyJTSVpFX0ZJTFRFUl9MQVJHRSJdXSA8LSBzZXVyYXRfQ1JDMiRjZWxsX2FyZWEgPCBxdWFudGlsZShzZXVyYXRfQ1JDMiRjZWxsX2FyZWEsIC45OSkKYGBgCgpOb3cgd2UgY2FuIHVzZSAqSW1hZ2VEaW1QbG90KiB0byB2aXN1YWxpc2UgdGhlIGNlbGxzIHdoaWNoIGhhdmUgYmVlbiBmbGFnZ2VkIGZvciByZW1vdmFsLgoKV2UgY2FuIHNlZSB0aGF0IHRoZXNlIGFyZSBtb3N0bHkgaW4gdGhlIHN1Ym11Y29zYSByZWdpb24uIAoKKipIb3cgZG8gZGlmZmVyZW50IHRocmVzaG9sZHMgYmVoYXZlPyBJcyB0aGVyZSBhIG1vcmUgYXBwcm9wcmlhdGUgb25lIHRvIHVzZT8gSXMgYW55IG5lY2Vzc2FyeSBhdCBhbGw/KioKCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGdyb3VwLmJ5PSJTSVpFX0ZJTFRFUl9MQVJHRSIpCmBgYApXZSBjYW4gdXNlIHRoZSBzYW1lIGFwcHJvYWNoIHRvIGNyZWF0ZSBhIGZpbHRlciBmb3Igc2VnbWVudGVkIGNlbGxzIHdoaWNoIGFyZSB2ZXJ5IHNtYWxsIGFuZCBsaWtlbHkgc2VnbWVudGF0aW9uIGFyZmV0YWN0cy4gCgoqcXVhbnRpbGUoc2V1cmF0JGNlbGxfYXJlYSwgMC4wMSkqOiBDYWxjdWxhdGVzIHRoZSAxc3QgcGVyY2VudGlsZSBvZiB0aGUgY2VsbF9hcmVhIHZhbHVlcyBpbiB0aGUgU2V1cmF0IG9iamVjdC4gVGhpcyB2YWx1ZSBzZXJ2ZXMgYXMgYSB0aHJlc2hvbGQgdG8gaWRlbnRpZnkgdGhlIHNtYWxsZXN0IDElIG9mIGNlbGxzLgoKKnNldXJhdCRjZWxsX2FyZWEgPiBxdWFudGlsZShzZXVyYXQkY2VsbF9hcmVhLCAwLjAxKSo6IENvbXBhcmVzIGVhY2ggY2VsbCdzIGFyZWEgdG8gdGhlIDFzdCBwZXJjZW50aWxlIHRocmVzaG9sZC4gVGhlIHJlc3VsdCBpcyBhIGxvZ2ljYWwgdmVjdG9yIHdoZXJlIGVhY2ggZWxlbWVudCBpcyBUUlVFIGlmIHRoZSBjb3JyZXNwb25kaW5nIGNlbGwncyBhcmVhIGlzIGdyZWF0ZXIgdGhhbiB0aGUgMXN0IHBlcmNlbnRpbGUgYW5kIEZBTFNFIG90aGVyd2lzZS4KCipzZXVyYXRbWyJTSVpFX0ZJTFRFUl9TTUFMTCJdXSo6IENyZWF0ZXMgYSBuZXcgbWV0YWRhdGEgZmllbGQgbmFtZWQgU0laRV9GSUxURVJfU01BTEwgaW4gdGhlIFNldXJhdCBvYmplY3QsIHN0b3JpbmcgdGhlIGxvZ2ljYWwgdmVjdG9yLgoKCmBgYHtyfQpzZXVyYXRfQ1JDMltbIlNJWkVfRklMVEVSX1NNQUxMIl1dIDwtIHNldXJhdF9DUkMyJGNlbGxfYXJlYSA+IHF1YW50aWxlKHNldXJhdF9DUkMyJGNlbGxfYXJlYSwgLjAxKQpgYGAKCk5vdyB3ZSBjYW4gdXNlICpJbWFnZURpbVBsb3QqIHRvIHZpc3VhbGlzZSB0aGUgY2VsbHMgd2hpY2ggaGF2ZSBiZWVuIGZsYWdnZWQgZm9yIHJlbW92YWwuCgpXZSBjYW4gc2VlIHRoYXQgdGhlc2UgYXJlIG1vcmUgc2NhdHRlcmVkIHRocm91Z2hvdXQgdGhlIHRpc3N1ZSAtIGJ1dCB0aGVyZSBtYXkgYmUgbW9yZSBpbiB0aGUgZm9sbGljdWxhciByZWdpb25zLiAKCioqSG93IGRvIGRpZmZlcmVudCB0aHJlc2hvbGRzIGJlaGF2ZT8gSXMgdGhlcmUgYSBtb3JlIGFwcHJvcHJpYXRlIG9uZSB0byB1c2U/IElzIGFueSBuZWNlc3NhcnkgYXQgYWxsPyoqCgpgYGB7cn0KSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMyLCBncm91cC5ieT0iU0laRV9GSUxURVJfU01BTEwiKQpgYGAKV2UgY2FuIGNoZWNrIGhvdyB0aGVzZSB2YWx1ZXMgY29ycmVsYXRlIHdpdGggZ2VuZSBkZXRlY3Rpb24gcmF0ZS4gCgpJZiB3ZSBmaWx0ZXIgb3V0IHNtYWxsIGNlbGxzLCB3ZSB3aWxsIHJlbW92ZSBjZWxscyB3aXRoIGxvdyBudW1iZXJzIG9mIGdlbmVzIGRldGVjdGVkLiAKCklmIHdlIGZpbHRlciBvdXQgbGFyZ2UgY2VsbHMsIHRoaXMgaXMgbm90IHRoYXQgYmlhc2VkIHRvd2FyZHMgb3Zlcmx5IGxhcmdlIGNvdW50cywgYXMgd2Ugc2F3IGJlZm9yZS4KCgpgYGB7ciBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9N30KcDEgPC0gVmxuUGxvdChzZXVyYXRfQ1JDMiwgIm5GZWF0dXJlX1hFTklVTSIsIGdyb3VwLmJ5ID0gIlNJWkVfRklMVEVSX1NNQUxMIiwgcHQuc2l6ZSA9IC4xLCBhbHBoYSA9IC41KSArIGxhYnModGl0bGU9IlNtYWxsIENlbGwgRmlsdGVyIikKcDIgPC0gVmxuUGxvdChzZXVyYXRfQ1JDMiwgIm5GZWF0dXJlX1hFTklVTSIsIGdyb3VwLmJ5ID0gIlNJWkVfRklMVEVSX0xBUkdFIiwgcHQuc2l6ZSA9IC4xLCBhbHBoYSA9IC41KSsgbGFicyh0aXRsZT0iTGFyZ2UgQ2VsbCBGaWx0ZXIiKQoKcDEgKyBwMgpgYGAKQWRqdXN0aW5nIHRoZSB0aHJlc2hvbGQgZm9yIHdoYXQgaXMgY29uc2lkZXJlZCBhICJzbWFsbCBjZWxsIiBjYW4gaGF2ZSBzaWduaWZpY2FudCBpbXBsaWNhdGlvbnMgZm9yIHlvdXIgYW5hbHlzaXMsIGVzcGVjaWFsbHkgaW4gYXJlYXMgd2l0aCBzcGVjaWZpYyBjZWxsIHR5cGVzIHN1Y2ggYXMgVC1jZWxscywgd2hpY2ggYXJlIHNtYWxsIGFuZCBkZW5zZWx5IHBhY2tlZCBpbiBmb2xsaWN1bGFyIHJlZ2lvbnMuIFRoaXMgZXhhbXBsZSBkZW1vbnN0cmF0ZXMgaG93IGNoYW5naW5nIHRoZSB0aHJlc2hvbGQgdG8gdGhlIDEwdGggcGVyY2VudGlsZSBhZmZlY3RzIHRoZSBmaWx0ZXJpbmcuIEluIHRoaXMgY2FzZSwgd2Ugd291bGQgcHJvYmFibHkgZmlsdGVyIG91dCBhIGxvdCBvZiBnb29kIGNlbGxzIHRoYXQgd2UgZG9uJ3Qgd2FudCB0byBsb3NlISBTbywgYmUgY2FyZWZ1bCB3aGVuIGxvb2tpbmcgYXQgdGhlc2UgdHlwZXMgb2YgUUMgbWV0cmljcyEKCgpgYGB7cn0Kc2V1cmF0X0NSQzJbWyJTSVpFX0ZJTFRFUl9TTUFMTCJdXSA8LSBzZXVyYXRfQ1JDMiRjZWxsX2FyZWEgPiBxdWFudGlsZShzZXVyYXRfQ1JDMiRjZWxsX2FyZWEsIC4xKQpgYGAKCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGdyb3VwLmJ5PSJTSVpFX0ZJTFRFUl9TTUFMTCIpCmBgYApMZXRzIHNldCB0aGlzIGJhY2sgdG8gdGhlIG9yaWdpbmFsIDElIHRocmVzaG9sZC4KYGBge3J9CnNldXJhdF9DUkMyW1siU0laRV9GSUxURVJfU01BTEwiXV0gPC0gc2V1cmF0X0NSQzIkY2VsbF9hcmVhID4gcXVhbnRpbGUoc2V1cmF0X0NSQzIkY2VsbF9hcmVhLCAuMDEpCmBgYAoKClRoZSBtb3N0IGltcG9ydGFudCBmaWx0ZXIgaXMgdGhlIG92ZXJhbGwgdHJhbnNjcmlwdCBkZXRlY3Rpb24uIEVtcHR5IGNlbGxzIG9yIGNlbGxzIHdpdGggdmVyeSBsb3cgdHJhbnNjcmlwdCBjb3VudCBjYW5ub3QgYmUgdGFrZW4gZm9yd2FyZCBmb3IgY2x1c3RlcmluZyBhbmFseXNpcyBhbmQgaXQgaXMgZXh0cmVtZWx5IGRpZmZpY3VsdCB0byBpZGVudGlmeSB3aGF0IHRoZXkgbWF5IGJlLiBIZXJlLCB3ZSBzZXQgYSB0aHJlc2hvbGQgb2YgbWluaW11bSAxNSB0cmFuc2NyaXB0cy4gVGhpcyBzZWVtcyBxdWl0ZSBsb3cgLSBmb3IgZGF0YSBmcm9tICppbiBzaXR1KiBwbGF0Zm9ybXMgd2l0aCBsb3cgbm9pc2UgKFhlbml1bSwgTWVyZmlzaCwgTWVyc2NvcGUpLCB0aGlzIGlzIGdlbmVyYWxseSBlbm91Z2ggdG8gY2x1c3RlciBhbmQgaWRlbnRpZnkgY2VsbCB0eXBlcy4gSWYgeW91ciBkYXRhIGhhcyBtb3JlIG5vaXNlIChlLmcuIENvc014KSwgYSBoaWdoZXIgdGhyZXNob2xkIGlzIG1vcmUgYXBwcm9wcmlhdGUuCgoKKnNldXJhdCRuQ291bnRfWEVOSVVNID49IDE1KjogQ29tcGFyZXMgZWFjaCBjZWxsJ3MgdHJhbnNjcmlwdCBjb3VudCB0byB0aGUgdGhyZXNob2xkIG9mIDE1LiBUaGUgcmVzdWx0IGlzIGEgbG9naWNhbCB2ZWN0b3Igd2hlcmUgZWFjaCBlbGVtZW50IGlzIFRSVUUgaWYgdGhlIGNvcnJlc3BvbmRpbmcgY2VsbCBoYXMgYXQgbGVhc3QgMTUgdHJhbnNjcmlwdHMgYW5kIEZBTFNFIG90aGVyd2lzZS4KKnNldXJhdCRUUkFOU0NSSVBUX0ZJTFRFUio6IENyZWF0ZXMgYSBuZXcgbWV0YWRhdGEgZmllbGQgbmFtZWQgVFJBTlNDUklQVF9GSUxURVIgaW4gdGhlIFNldXJhdCBvYmplY3QsIHN0b3JpbmcgdGhlIGxvZ2ljYWwgdmVjdG9yLgoKCmBgYHtyfQpzZXVyYXRfQ1JDMiRUUkFOU0NSSVBUX0ZJTFRFUiA8LSBzZXVyYXRfQ1JDMiRuQ291bnRfWEVOSVVNID49IDE1CmBgYAoKQW5kIHdlIGNhbiB2aXN1YWxpc2UgdGhlIGNlbGxzIHRoYXQgd2Ugd291bGQgbG9zZS4gCgpXZSBzZWUgdGhhdCB3ZSBkaXNwcm9wb3J0aW9uYXRlbHkgd291bGQgZmlsdGVyIG91dCBtb3JlIGNlbGxzIGZyb20gc29tZSByZWdpb25zIHRoYW4gb3RoZXJzLiBBcyBwb2ludGVkIG91dCBwcmV2aW91c2x5LCB0aGlzIGlzIGxpa2VseSBkdWUgdG8gYSBjb21iaW5hdGlvbiBvZiBnZW5lIHBhbmVsIGNvdmVyYWdlIGluIHNvbWUgcmVnaW9ucyBhbmQgdmVyeSBzbWFsbCBjZWxscyBpbiBkZW5zZWx5IHBhY2tlZCByZWdpb25zIGxpa2UgZm9sbGljbGVzLgoKYGBge3J9CkltYWdlRGltUGxvdChzZXVyYXRfQ1JDMiwgZ3JvdXAuYnk9IlRSQU5TQ1JJUFRfRklMVEVSIikKYGBgCkZpbmFsbHksIHZpc3VhbGl6aW5nIHRoZSBjb3VudHMgb2YgbmVnYXRpdmUgY29udHJvbCBjb2Rld29yZHMsIG5lZ2F0aXZlIGNvbnRyb2wgcHJvYmVzLCBhbmQgdW5hc3NpZ25lZCBjb2Rld29yZHMgaGVscHMgaWRlbnRpZnkgYW5kIHVuZGVyc3RhbmQgdGVjaG5pY2FsIGFydGlmYWN0cyBhbmQgYmFja2dyb3VuZCBub2lzZSBpbiB5b3VyIHNwYXRpYWwgdHJhbnNjcmlwdG9taWNzIGRhdGEuCgpIZXJlLCB3ZSBjYW4gc2VlIHRoYXQgYWxsIGNvbnRyb2wgcHJvYmVzIGFuZCBjb2Rld29yZHMgcHJvZHVjZSB5aWVsZCB2ZXJ5IGxpdHRsZSBzaWduYWwsIHN1Z2dlc3Rpbmcgb3VyIGRhdGEgaXMgZ29vZCBxdWFsaXR5ISAKCkluIHNvbWUgY2FzZXMsIGhpZ2ggYW1vdW50IG9mIGF1dG9mbG91cmVzY2VuY2UgaXMgdGhlIGNlbGxzL3Rpc3N1ZSBjYW4gc29tZXRpbWVzIGdlbmVyYXRlIGZhbHNlIHBvc2l0aXZlIHNpZ25hbCBhbmQgdGhpcyBzaG91bGQgYmUgZmlsdGVyZWQgb3V0LiAKCmBgYHtyIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTd9CkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJuQ291bnRfTmVnYXRpdmUuQ29udHJvbC5Db2Rld29yZCIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAibkNvdW50X05lZ2F0aXZlLkNvbnRyb2wuUHJvYmUiKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMiwgIm5Db3VudF9VbmFzc2lnbmVkLkNvZGV3b3JkIikgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCmBgYAoKQWx0aG91Z2ggdGhlIG5lZ2F0aXZlIGNvbnRyb2wgc2lnbmFsIGlzIGxvdywgd2UgY2FuIG5vbmV0aGVsZXNzIGNyZWF0ZSBhIGZpbHRlciB0byByZW1vdmUgY2VsbHMgd2hpY2ggaGF2ZSBhbnksIGFsdGhvdWdoIGluIHRoaXMgY2FzZSBpdCBpcyBwcm9iYWJseSB1bm5lY2Vzc2FyeS4KCmBgYHtyfQpzZXVyYXRfQ1JDMiRQUk9CRV9GSUxURVIgPC0gc2V1cmF0X0NSQzIkbkNvdW50X1VuYXNzaWduZWQuQ29kZXdvcmQgPT0gMCAmCiAgICAgICAgICAgICAgICAgICAgICAgc2V1cmF0X0NSQzIkbkNvdW50X05lZ2F0aXZlLkNvbnRyb2wuQ29kZXdvcmQgPT0gMCAmCiAgICAgICAgICAgICAgICAgICAgICAgc2V1cmF0X0NSQzIkbkNvdW50X05lZ2F0aXZlLkNvbnRyb2wuUHJvYmUgPT0gMApgYGAKCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGdyb3VwLmJ5PSJQUk9CRV9GSUxURVIiKQpgYGAKRmluYWxseSwgd2UgY2FuIHN1YnNldCB0aGUgc2V1cmF0IG9iamVjdCBiYXNlZCBvbiBhbnkvYWxsIG9mIHRoZSBmaWx0ZXJzIHdlIGhhdmUgY3JlYXRlZCBlYXJsaWVyLiAKCkJ5IGNvbWJpbmluZyBwcm9iZSwgc2l6ZSwgYW5kIHRyYW5zY3JpcHQgZmlsdGVycywgeW91IGNhbiByZXRhaW4gb25seSB0aGUgY2VsbHMgdGhhdCBtZWV0IGFsbCBxdWFsaXR5IGNyaXRlcmlhLCByZWR1Y2luZyB0aGUgaW1wYWN0IG9mIHRlY2huaWNhbCBhcnRpZmFjdHMgYW5kIG5vaXNlIG9uIHlvdXIgYW5hbHlzaXMuCgpgYGB7cn0Kc2V1cmF0X0NSQzIgPC0gc3Vic2V0KHNldXJhdF9DUkMyLCBQUk9CRV9GSUxURVIgJiBTSVpFX0ZJTFRFUl9MQVJHRSAmIFNJWkVfRklMVEVSX1NNQUxMICYgVFJBTlNDUklQVF9GSUxURVIpCmBgYApMZXRzIGV4YW1pbmUgdGhlIGNsZWFuZWQgdXAgb2JqZWN0IC0gd2UgaGF2ZSBsb3N0IGEgZmV3IHRob3VzYW5kIGNlbGxzIGZyb20gdGhlIGFuYWx5c2lzLiAKYGBge3J9CnNldXJhdApJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIpCnNhdmVSRFMoc2V1cmF0X0NSQzIsIGZpbGU9IkNSQzJfc3Vic2V0X2ZpbHRlcmVkLlJEUyIpCmBgYAoqKkRhdGEgTm9ybWFsaXNhdGlvbioqCgpUaGUgKlNDVHJhbnNmb3JtKiBmdW5jdGlvbiBpbiBTZXVyYXQgaXMgdXNlZCBmb3Igbm9ybWFsaXppbmcgc2luZ2xlLWNlbGwgUk5BLXNlcSBhbmQgc3BhdGlhbCB0cmFuc2NyaXB0b21pY3MgZGF0YS4gVGhpcyBtZXRob2QgbW9kZWxzIHRoZSBnZW5lIGV4cHJlc3Npb24gY291bnRzIHVzaW5nIGEgcmVndWxhcml6ZWQgbmVnYXRpdmUgYmlub21pYWwgcmVncmVzc2lvbiBhbmQgcmVtb3ZlcyB0ZWNobmljYWwgbm9pc2Ugd2hpbGUgcHJlc2VydmluZyBiaW9sb2dpY2FsIHZhcmlhYmlsaXR5LiBUaGUgKmNsaXAucmFuZ2UqIHBhcmFtZXRlciBpcyB1c2VkIHRvIGxpbWl0IHRoZSByYW5nZSBvZiB0aGUgdHJhbnNmb3JtZWQgdmFsdWVzLCB3aGljaCBjYW4gaGVscCBzdGFiaWxpemUgZG93bnN0cmVhbSBhbmFseXNlcyBieSBsaW1pdGluZyB0aGUgaW5mbHVlbmNlIG9mIGV4dHJlbWUgdmFsdWVzLiAKCgpgYGB7cn0Kc2V1cmF0X0NSQzIgPC0gU0NUcmFuc2Zvcm0oc2V1cmF0X0NSQzIsIGFzc2F5ID0gIlhFTklVTSIsIGNsaXAucmFuZ2UgPSBjKC0xMCwgMTApKQpgYGAKClByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSkgaXMgYSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gdGVjaG5pcXVlIHVzZWQgdG8gaWRlbnRpZnkgdGhlIHByaW1hcnkgYXhlcyBvZiB2YXJpYXRpb24gaW4gaGlnaC1kaW1lbnNpb25hbCBkYXRhLiBJbiB0aGUgY29udGV4dCBvZiBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcywgUENBIGhlbHBzIHRvIHJlZHVjZSB0aGUgY29tcGxleGl0eSBvZiB0aGUgZGF0YSB3aGlsZSBwcmVzZXJ2aW5nIHRoZSBtb3N0IGltcG9ydGFudCBwYXR0ZXJucyBvZiB2YXJpYXRpb24uIAoKClRJUDogSWYgeW91ciB0YXJnZXQgcGFuZWwgaXMgdmVyeSBzbWFsbCwgeW91IGNhbiBza2lwIHRoaXMgc3RlcCBhbmQgY2Fycnkgb3V0IGNsdXN0ZXJpbmcgYW5hbHlzaXMgZGlyZWN0bHkgb24gZ2VuZSBleHByZXNzaW9uLiBUaGlzIGNhbiBzb21ldGltZXMgaGVscCB3aXRoIGFjaGlldmluZyBiZXR0ZXIgY2x1c3RlcmluZyByZXN1bHRzLgpgYGB7cn0Kc2V1cmF0X0NSQzIgPC0gUnVuUENBKHNldXJhdF9DUkMyKQpgYGAKQXMgYmVmb3JlLCB3ZSBjYW4gdmlzdWFsaXNlIGhvdyBtdWNoIHZhcmlhdGlvbiBpcyBjYXB0dXJlZCBieSBlYWNoIFBDLiAKClRoZSBFbGJvd1Bsb3QgZnVuY3Rpb24gaGVscHMgdG8gZGV0ZXJtaW5lIHRoZSBudW1iZXIgb2Ygc2lnbmlmaWNhbnQgUENzIHRvIHVzZSBmb3IgZG93bnN0cmVhbSBhbmFseXNlcy4gVGhlIHBsb3QgdHlwaWNhbGx5IHNob3dzIHRoZSBhbW91bnQgb2YgdmFyaWFuY2UgZXhwbGFpbmVkIGJ5IGVhY2ggUEMsIGFuZCB0aGUgImVsYm93IiBwb2ludCBpbmRpY2F0ZXMgYSBuYXR1cmFsIGN1dG9mZi4KCgpgYGB7cn0KRWxib3dQbG90KHNldXJhdF9DUkMyLCA1MCkKYGBgClBsb3R0aW5nIHRoZSB0b3AgZ2VuZXMgY29udHJpYnV0aW5nIHRvIGEgc3BlY2lmaWMgcHJpbmNpcGFsIGNvbXBvbmVudCBoZWxwcyBpbiB1bmRlcnN0YW5kaW5nIHRoZSBiaW9sb2dpY2FsIGZhY3RvcnMgZHJpdmluZyB0aGUgdmFyaWF0aW9uIGNhcHR1cmVkIGJ5IHRoYXQgY29tcG9uZW50LiBUaGlzIHR5cGUgb2YgcGxvdCBoaWdobGlnaHRzIHRoZSBnZW5lcyB3aXRoIHRoZSBoaWdoZXN0IGxvYWRpbmdzLCB3aGljaCBhcmUgdGhlIG1vc3QgaW5mbHVlbnRpYWwgaW4gdGhlIHByaW5jaXBhbCBjb21wb25lbnQgYW5hbHlzaXMuCgpgYGB7ciBmaWcuaGVpZ2h0PTksIGZpZy53aWR0aD03fQpQQ19QbG90dGluZyhzZXVyYXRfQ1JDMiwgZGltX251bWJlciA9IDEpCmBgYAoKVGhlICpGZWF0dXJlUGxvdCogZnVuY3Rpb24gaW4gU2V1cmF0IGlzIHVzZWQgdG8gdmlzdWFsaXplIHRoZSBleHByZXNzaW9uIG9mIGEgc3BlY2lmaWMgZ2VuZSBhY3Jvc3MgY2VsbHMgaW4gYSBnaXZlbiBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gc3BhY2UgKGUuZy4sIFBDQSkuIFRoaXMgaGVscHMgdG8gdW5kZXJzdGFuZCBob3cgdGhlIGV4cHJlc3Npb24gb2YgYSBnZW5lIHZhcmllcyBhY3Jvc3MgdGhlIHByaW5jaXBhbCBjb21wb25lbnRzLgoKYGBge3J9CkZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiQ0VBQ0FNNSIsIHJlZHVjdGlvbiA9ICJwY2EiKSArIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpCmBgYApXZSBjYW4gYWxzbyBleGFtaW5lIGhvdyB2YXJpb3VzIFBDcyBhcmUgZGlzdHJpYnV0ZWQgc3BhdGlhbGx5LiAKCkhlcmUsIHdlIGNhbiBzZWUgdGhhdCBoaWdoIFBDMSBsb2FkaW5ncyBlbnJpY2ggaW4gZm9sbGljdWxhciBzdHJ1Y3R1cmVzIGFuZCBsb3cgUEMxIGxvYWRpbmdzIGVucmljaCBpbiBjcnlwdCB0b3AgY2VsbHMuCmBgYHtyfQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiUENfMSIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKCldlIGNhbiBwbG90IHRoZSBleHByZXNzaW9uIG9mIGhpZ2ggKG9yIGxvdykgbG9hZGluZyBnZW5lcyB0byB2aXN1YWxpc2UgaG93IHRoaXMgY29ycmVsYXRlcyB3aXRoIG91ciBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24uCmBgYHtyfQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiSUdGQlA3Iiwgc2l6ZT0uNSkgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCmBgYApOZXh0LCB3ZSB3aWxsIHVzZSB0aGUgcmVkdWNlZCBkaW1lbnNpb25hbGl0eSBkYXRhIGZvciBjbHVzdGVyaW5nIGFuZCBjbHVzdGVyIHZpc3VhbGlzYXRpb24uIAoKKlJ1blVNQVAqOiBQZXJmb3JtIFVuaWZvcm0gTWFuaWZvbGQgQXBwcm94aW1hdGlvbiBhbmQgUHJvamVjdGlvbiAoVU1BUCkgdG8gcmVkdWNlIHRoZSBkaW1lbnNpb25hbGl0eSBvZiB0aGUgZGF0YSBmb3IgdmlzdWFsaXphdGlvbi4gVGhlIFVNQVAgcGxvdCByZWR1Y2VzIHRoZSBoaWdoLWRpbWVuc2lvbmFsIGRhdGEgdG8gdHdvIGRpbWVuc2lvbnMsIHByZXNlcnZpbmcgdGhlIGxvY2FsIGFuZCBnbG9iYWwgc3RydWN0dXJlIG9mIHRoZSBkYXRhIGZvciB2aXN1YWxpemF0aW9uLiBDZWxscyB0aGF0IGFyZSBjbG9zZSB0b2dldGhlciBpbiB0aGUgVU1BUCBwbG90IGFyZSBzaW1pbGFyIGluIHRoZWlyIGdlbmUgZXhwcmVzc2lvbiBwcm9maWxlcy4KKnNldXJhdCo6IFRoZSBTZXVyYXQgb2JqZWN0LgoqZGltcyA9IDE6MjAqOiBTcGVjaWZpZXMgdGhlIHByaW5jaXBhbCBjb21wb25lbnRzIHRvIHVzZSBmb3IgVU1BUC4KCipGaW5kTmVpZ2hib3JzKjogRmluZGluZyBuZWFyZXN0IG5laWdoYm9ycyBoZWxwcyB0byBpZGVudGlmeSBjZWxscyB0aGF0IGFyZSBzaW1pbGFyIGJhc2VkIG9uIHRoZWlyIFBDQSBzY29yZXMsIHdoaWNoIGlzIHVzZWQgZm9yIGNsdXN0ZXJpbmcuCipzZXVyYXQqOiBUaGUgU2V1cmF0IG9iamVjdC4KKnJlZHVjdGlvbiA9ICJwY2EiKjogU3BlY2lmaWVzIHRoYXQgdGhlIFBDQSBzcGFjZSBzaG91bGQgYmUgdXNlZCBmb3IgZmluZGluZyBuZWlnaGJvcnMuCipkaW1zID0gMToyMCo6IFNwZWNpZmllcyB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudHMgdG8gdXNlIGZvciBpZGVudGlmeWluZyBuZWlnaGJvcnMuCgoqRmluZENsdXN0ZXJzKjogQ2x1c3RlcmluZyBpZGVudGlmaWVzIGRpc3RpbmN0IGdyb3VwcyBvZiBjZWxscyB3aXRoIHNpbWlsYXIgZ2VuZSBleHByZXNzaW9uIHBhdHRlcm5zLiBUaGUgcmVzb2x1dGlvbiBwYXJhbWV0ZXIgY29udHJvbHMgdGhlIGdyYW51bGFyaXR5IG9mIHRoZSBjbHVzdGVyaW5nLgoqc2V1cmF0KjogVGhlIFNldXJhdCBvYmplY3QuCipyZXNvbHV0aW9uID0gMC43KjogU2V0cyB0aGUgcmVzb2x1dGlvbiBwYXJhbWV0ZXIgZm9yIGNsdXN0ZXJpbmcuIEhpZ2hlciB2YWx1ZXMgbGVhZCB0byBtb3JlIGNsdXN0ZXJzLCB3aGlsZSBsb3dlciB2YWx1ZXMgbGVhZCB0byBmZXdlciBjbHVzdGVycy4KCmBgYHtyfQpzZXVyYXRfQ1JDMiA8LSBSdW5VTUFQKHNldXJhdF9DUkMyLCBkaW1zID0gMToyMCkKc2V1cmF0X0NSQzIgPC0gRmluZE5laWdoYm9ycyhzZXVyYXRfQ1JDMiwgcmVkdWN0aW9uID0gInBjYSIsIGRpbXMgPSAxOjIwKQpzZXVyYXRfQ1JDMiA8LSBGaW5kQ2x1c3RlcnMoc2V1cmF0X0NSQzIsIHJlc29sdXRpb24gPSAwLjIpCnNldXJhdF9DUkMyIDwtIEZpbmRDbHVzdGVycyhzZXVyYXRfQ1JDMiwgcmVzb2x1dGlvbiA9IDAuNCkKc2V1cmF0X0NSQzIgPC0gRmluZENsdXN0ZXJzKHNldXJhdF9DUkMyLCByZXNvbHV0aW9uID0gMC42KQpzZXVyYXRfQ1JDMiA8LSBGaW5kQ2x1c3RlcnMoc2V1cmF0X0NSQzIsIHJlc29sdXRpb24gPSAwLjgpCnNldXJhdF9DUkMyIDwtIEZpbmRDbHVzdGVycyhzZXVyYXRfQ1JDMiwgcmVzb2x1dGlvbiA9IDEuMCkKc2V1cmF0X0NSQzIgPC0gRmluZENsdXN0ZXJzKHNldXJhdF9DUkMyLCByZXNvbHV0aW9uID0gMC4zKQpjbHVzdHJlZShzZXVyYXRfQ1JDMikKbGlicmFyeShjbHVzdHJlZSkKYGBgCgpOZXh0IGxldHMgdmlzdWFsaXNlIHRoZSBjbHVzdGVycyAtIGZpcnN0bHksIGJhc2VkIG9uIHRyYW5zY3JpcHRvbWUgZW1iZWRkaW5nLgoKKkRpbVBsb3QqOiBDcmVhdGVzIGEgc2NhdHRlciBwbG90IG9mIGNlbGxzIGluIGEgcmVkdWNlZC1kaW1lbnNpb25hbCBzcGFjZSwgYnkgZGVmYXVsdCBub3cgdXNpbmcgVU1BUCBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24uCipzZXVyYXQqOiBUaGUgU2V1cmF0IG9iamVjdCBjb250YWluaW5nIHRoZSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gcmVzdWx0cyBhbmQgY2x1c3RlciBhc3NpZ25tZW50cy4KKmxhYmVsID0gVFJVRSo6IEFkZHMgY2x1c3RlciBsYWJlbHMgdG8gdGhlIHBsb3QuCipyZXBlbCA9IFRSVUUqOiBSZXBlbHMgdGhlIGxhYmVscyB0byBhdm9pZCBvdmVybGFwcGluZywgbWFraW5nIHRoZSBwbG90IGNsZWFyZXIuCgoKYGBge3J9CkRpbVBsb3Qoc2V1cmF0X0NSQzIsIGxhYmVsPVQsIHJlcGVsPVQpCnNldXJhdF9DUkMyJFNDVF9zbm5fcmVzLjAuMgpgYGAKQW5kIG5vdyBsZXRzIHBsb3QgdGhlIGNsdXN0ZXJzIGluIHRpc3N1ZSBzcGFjZS4gCgpXZSBjYW4gc2VlIHRoYXQgb3VyIGNsdXN0ZXJzIGhhdmUgcXVpdGUgbmljZSBjb3JyZXNwb25kZW5jZSB0byBkaXN0aW5jdCBzcGF0aWFsIHJlZ2lvbnMuCmBgYHtyfQoKSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMyLCBzaXplPS41KQpgYGAKQXMgYmVmb3JlLCBub3cgd2UgY2FuIHVzZSBTZXVyYXQgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gZnVuY3Rpb25zIHRvIGlkZW50aWZ5IG1hcmtlciBnZW5lcyBmb3Igc3BlY2lmaWMgY2VsbCBjbHVzdGVycy4KCipGaW5kTWFya2Vycyo6IElkZW50aWZpZXMgZ2VuZXMgdGhhdCBhcmUgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGluIGEgc3BlY2lmaWVkIGNsdXN0ZXIgY29tcGFyZWQgdG8gYWxsIG90aGVyIGNlbGxzLgoqc2V1cmF0KjogVGhlIFNldXJhdCBvYmplY3QgY29udGFpbmluZyB0aGUgZ2VuZSBleHByZXNzaW9uIGRhdGEgYW5kIGNsdXN0ZXIgaWRlbnRpdGllcy4KKmlkZW50LjEgPSAiMCIqOiBTcGVjaWZpZXMgdGhlIGNsdXN0ZXIgb2YgaW50ZXJlc3QgZm9yIHdoaWNoIG1hcmtlciBnZW5lcyBhcmUgdG8gYmUgaWRlbnRpZmllZC4gSW4gdGhpcyBjYXNlLCBjbHVzdGVyICIwIi4KKm1heC5jZWxscy5wZXIuaWRlbnQgPSA1MDAqOiBMaW1pdHMgdGhlIG51bWJlciBvZiBjZWxscyB0byBiZSB1c2VkIGZyb20gZWFjaCBjbHVzdGVyIGZvciB0aGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgdG8gNTAwLiBUaGlzIGNhbiBoZWxwIHRvIHNwZWVkIHVwIHRoZSBjb21wdXRhdGlvbi4KCgpgYGB7cn0KbWFya2VycyA8LSBGaW5kTWFya2VycyhzZXVyYXRfQ1JDMiwgaWRlbnQuMT0iMCIsIG1heC5jZWxscy5wZXIuaWRlbnQ9NTAwKQpgYGAKCmBgYHtyfQpoZWFkKG1hcmtlcnMpCmBgYAoKV2UgY2FuIHZpc3VhbGlzZSBleHByZXNzaW9uIG9mIGNsdXN0ZXIgc3BlY2lmaWMgbWFya2VycyB1c2luZyBmZWF0dXJlIHBsb3RzCmBgYHtyfQpGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMiwgIkNEM0UiLCBsYWJlbD1ULCByZXBlbD1UKSsgc2NhbGVfY29sb3JfdmlyaWRpc19jKGRpcmVjdGlvbj0tMSkgI3QgY2VsbApGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMiwgIk1TNEExIiwgbGFiZWw9VCwgcmVwZWw9VCkrICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoZGlyZWN0aW9uPS0xKSAjQiBjZWxsCkZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiQ0VBQ0FNNSIsIGxhYmVsPVQsIHJlcGVsPVQpKyBzY2FsZV9jb2xvcl92aXJpZGlzX2MoZGlyZWN0aW9uPS0xKSAjY29sb3JlY3RhbCBjYW5jZXIgYW5kIG5vbi1zbWFsbC1jZWxsIGx1bmcgY2FuY2VyCkZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiS0lUIiwgbGFiZWw9VCwgcmVwZWw9VCkrIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYyhkaXJlY3Rpb249LTEpCiNoZSBLSVQgZ2VuZSBpcyBhIGNlbGwgc3VyZmFjZSBtYXJrZXIgYW5kIHByZWRpY3RpdmUgYmlvbWFya2VyIHRoYXQgY2FuIGJlIHVzZWQgZm9yIGEgdmFyaWV0eSBvZiBwdXJwb3NlcywgaW5jbHVkaW5nOiAKI2FuY2VyIGRpYWdub3NpcwojS0lUIGdlbmUgbXV0YXRpb25zIGNhbiBiZSB1c2VkIHRvIGRpYWdub3NlLCBwcmVkaWN0LCBhbmQgcHJvdmlkZSBwcm9nbm9zdGljIGluZm9ybWF0aW9uIGZvciBjZXJ0YWluIGNhbmNlcnMsIHN1Y2ggYXMgYWN1dGUgbXllbG9pZCBsZXVrZW1pYSAoQU1MKSwgbWVsYW5vbWEsIGdhc3Ryb2ludGVzdGluYWwgc3Ryb21hbCB0dW1vcnMgKEdJU1QpLCBhbmQgc3lzdGVtaWMgbWFzdG9jeXRvc2lzIChTTSkKCkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJDRDNFIiwgc2l6ZT0uNSkgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJNUzRBMSIsIHNpemU9LjUpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiQ0VBQ0FNNSIsIHNpemU9LjUpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiS0lUIiwgc2l6ZT0uNSkgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCmBgYApPciwgYXMgaW4gb3VyIHNlcXVlbmNpbmcgU1QgdHV0b3JpYWwsIGRldGVjdCBhbmQgdmlzdWFsaXNlIHRvcCBtYXJrZXJzIGZvciBldmVyeSBjbHVzdGVyLgpgYGB7cn0KbWFya2VycyA8LSBGaW5kQWxsTWFya2VycyhzZXVyYXRfQ1JDMiwgbWF4LmNlbGxzLnBlci5pZGVudCA9IDUwMCkKYGBgCgpgYGB7cn0KaGVhZChtYXJrZXJzKQpgYGAKCnNjQ3VzdG9taXplIHBhY2thZ2UgcHJvdmlkZXMgYSBjb252ZW5pZW50IGhlbHBlciBmdW5jdGlvbiwgKkV4dHJhY3RfVG9wX01hcmtlcnMqLCB0byBleHRyYWN0IHRoZSB0b3AgbWFya2VyIGdlbmVzIGZvciBlYWNoIGNsdXN0ZXIgZnJvbSB0aGUgb3V0cHV0IG9mICpGaW5kQWxsTWFya2VycyouIFRoaXMgZnVuY3Rpb24gc2ltcGxpZmllcyB0aGUgcHJvY2VzcyBvZiBpZGVudGlmeWluZyBhbmQgcmV0cmlldmluZyB0aGUgbW9zdCBzaWduaWZpY2FudCBtYXJrZXIgZ2VuZXMgZm9yIGFuYWx5c2lzIGFuZCB2aXN1YWxpc2F0aW9uLgoKSW4gdGhpcyBjYXNlLCB3ZSBhcmUgZXh0cmFjdGluZyB0aGUgdG9wIGZpdmUgbWFya2VycyBwZXIgY2x1c3Rlci4KCmBgYHtyfQp0b3AgPC0gRXh0cmFjdF9Ub3BfTWFya2VycyhtYXJrZXJzLCBudW1fZ2VuZXMgPSA1LCBuYW1lZF92ZWN0b3IgPSBGQUxTRSwgbWFrZV91bmlxdWUgPSBUUlVFKQp0b3AKYGBgCgoqQ2x1c3RlcmVkX0RvdFBsb3QqIGZ1bmN0aW9uIGZyb20gdGhlICpzY0N1c3RvbWl6ZSogcGFja2FnZSBwcm92aWRlcyBhIGNvbnZlbmllbnQgYW5kIHZpc3VhbGx5IGFwcGVhbGluZyB3YXkgdG8gZGlzcGxheSBleHByZXNzaW9uIHBhdHRlcm5zIG9mIHRvcCBtYXJrZXIgZ2VuZXMgYWNyb3NzIGNsdXN0ZXJzIHVzaW5nIGEgZG90IHBsb3QuIFRoaXMgZnVuY3Rpb24gbm90IG9ubHkgcGxvdHMgdGhlIGV4cHJlc3Npb24gZGF0YSBidXQgYWxzbyBjbHVzdGVycyB0aGUgZ2VuZXMgYW5kIGdyb3VwcyBmb3IgZW5oYW5jZWQgdmlzdWFsIGludGVycHJldGF0aW9uLiBUaGlzIGlzIGFuIGFsdGVybmF0aXZlIHRvIFNldXJhdCAqRG90UGxvdCogZnVuY3Rpb24uIAoKKmsgPSAxOCo6IERldGVybWluZXMgdGhlIG51bWJlciBvZiBjbHVzdGVycyBmb3IgdGhlIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIG9mIGdlbmVzIHRvIGVuaGFuY2UgdmlzdWFsIHNlcGFyYXRpb24gb2YgZXhwcmVzc2lvbiBwYXR0ZXJucy4gCgpXZSBjYW4gc2VlIHRoYXQgbW9zdCBjbHVzdGVycyBoYXZlIHVuaXF1ZSBtYXJrZXJzLCB3aGljaCBzdWdnZXN0cyB0aGUgZGF0YXNldCBpcyBub3Qgb3Zlci1jbHVzdGVyZWQuCgpgYGB7ciBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9N30KQ2x1c3RlcmVkX0RvdFBsb3Qoc2V1cmF0X0NSQzIsIGZlYXR1cmVzID0gdG9wLCBrPTE4KQpgYGAKCioqQWRkaXRpb25hbCBTcGF0aWFsIFZpc3VhbGlzYXRpb25zKioKClRoZSByZXNvbHV0aW9uIG9mICppbiBzaXR1KiBkYXRhc2V0cyBpcyB0eXBpY2FsbHkgdmVyeSBoaWdoIGFuZCBzbyBpdCBjYW4gYmUgZGlmZmljdWx0IHRvIHZpc3VhbGlzZSBldmVyeXRoaW5nIGluIG9uZSBwbG90LiBCZWxvdywgd2Ugd2lsbCBleHBsb3JlIGRpZmZlcmVudCB2aXN1YWxpc2F0aW9ucyB0aGF0IGNhbiBoZWxwIHVucGljayBhbmQgdW5kZXJzdGFuZCB0aGUgZGF0YSBhIGJpdCBiZXR0ZXIuIAoKClRvIGJldHRlciB2aXN1YWxpc2Ugc3BhdGlhbCBkaXN0cmlidXRpb24gb2YgY2x1c3RlcnMsIHNvbWV0aW1lcyBpdCBjYW4gYmUgdXNlZnVsIHRvIHN1YnNldCBvbmx5IGNlcnRhaW4gZ3JvdXBzIHRvIHJlZHVjZSBjcm93ZGluZy4gIEhlcmUsIHdlIHNwZWNpZmljYWxseSBvbmx5IHZpc3VhbGlzaW5nIHR3byBzZWxlY3RlZCBjbHVzdGVycy4gCgoqV2hpY2hDZWxscyo6IElkZW50aWZpZXMgY2VsbHMgYmFzZWQgb24gc3BlY2lmaWVkIGNyaXRlcmlhLgoqc2V1cmF0KjogVGhlIFNldXJhdCBvYmplY3QuCipleHByZXNzaW9uID0gc2V1cmF0X2NsdXN0ZXJzICVpbiUgYygwLCA1KSo6IExvZ2ljYWwgZXhwcmVzc2lvbiB0byBzZWxlY3QgY2VsbHMgYmVsb25naW5nIHRvIGNsdXN0ZXJzIDAgYW5kIDUuCgoKKipUaGlzIHdvcmtzIHdpdGggKkltYWdlRmVhdHVyZVBsb3QqIHRvby4gVHJ5IGl0IHdpdGggc29tZSBnZW5lcyEqKgpgYGB7cn0KSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMyLCBjZWxscz1XaGljaENlbGxzKHNldXJhdF9DUkMyLCBleHByZXNzaW9uID0gc2V1cmF0X2NsdXN0ZXJzICVpbiUgYygwLCA1KSkpCmBgYAoKU29tZXRpbWVzLCBpdCBjYW4gYmUgdXNlZnVsIHRvIGNyZWF0ZSBhZGRpdGlvbmFsIGZpZWxkcyBvZiB2aWV3IG9mIHRoZSBkYXRhIC0gZm9yIGV4YW1wbGUsIHpvb21zIG9mIHNwZWNpZmljIHJlZ2lvbnMuIApGaXJzdCwgbGV0J3MgbG9vayBhdCB0aGUgY29vcmRpbmF0ZSBzeXN0ZW0gYnkgcGxvdHRpbmcgdGhlIGRhdGEgYW5kIHR1cm5pbmcgb24gdGhlIHBsb3R0aW5nIG9mIHRoZSBheGVzLCB3aGljaCBhcmUgb2ZmIGJ5IGRlZmF1bHQgdG8gY3JlYXRlIG5pY2VyIGxvb2tpbmcgcGxvdHMuIAoKVGhpcyBnaXZlcyB1cyBhIHJvdWdoIGlkZWEgb24gd2hlcmUgaW4gdGhlIGNvb3JkaW5hdGUgc3lzdGVtIHRvIGNyZWF0ZSBhbnkgc3Vic2V0cyBvciB6b29tcyBvZiB0aGUgZGF0YS4KCkZvciBleGFtcGxlLCBpZiB3ZSB3YW50IHRvIHpvb20gaW4gb24gdGhlIGZvbGxpY2xlIGluIHRoZSB0b3AgcmlnaHQgY29ybmVyLCB3ZSBjYW4gc2VlIHRoYXQgaXQgbGllcyByb3VnaGx5IGJldHdlZW4gNDAwMC01MDAwIGFuZCA4MDAwLTkwMDAgY29vcmRpbmF0ZSByZWdpb25zLiAKCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGF4ZXMgPSBUKQpgYGAKU28sIGxldCdzIGNyZWF0ZSBhIG5ldyBGT1Ygd2l0aCB0aGVzZSBjb29yZGluYXRlcy4gRm9yIHRoaXMsIHdlIGNhbiB1c2UgdGhlICpDcm9wKiBmdW5jdGlvbi4gCgoqc2V1cmF0W1siQ09MT04iXV0qOiBUaGUgc3BhdGlhbCBhc3NheSB0byBiZSBjcm9wcGVkLgoqeCA9IGMoNDIwMCwgNTAwMCkqOiBUaGUgeC1heGlzIHJhbmdlIGZvciB0aGUgY3JvcC4KKnkgPSBjKDgwMDAsIDg4MDApKjogVGhlIHktYXhpcyByYW5nZSBmb3IgdGhlIGNyb3AuCipjb29yZHMgPSAicGxvdCIqOiBTcGVjaWZpZXMgdGhlIGNvb3JkaW5hdGUgc3lzdGVtIHRvIHVzZSAodHlwaWNhbGx5ICJwbG90IiBmb3Igc3BhdGlhbCBjb29yZGluYXRlcykuCgoqc2V1cmF0W1siUk9JMSJdXSA8LSBjcm9wcGVkKjogQWRkcyB0aGUgY3JvcHBlZCByZWdpb24gYXMgYSBuZXcgRk9WIG5hbWVkICJST0kxIiBpbiB0aGUgU2V1cmF0IG9iamVjdC4gVGhpcyBjb3VsZCBiZSBhIG1vcmUgaW5mb3JtYXRpdmUgbmFtZSwgYnV0IGF2b2lkIHVzaW5nIHVuZGVyc2NvcmVzIQoKYGBge3J9CmNyb3BwZWQgPC0gQ3JvcChzZXVyYXRbWyJDT0xPTiJdXSwgeCA9IGMoNDIwMCwgNTAwMCksIHkgPSBjKDgwMDAsIDg4MDApLCBjb29yZHMgPSAicGxvdCIpCnNldXJhdFtbIlJPSTEiXV0gPC0gY3JvcHBlZApgYGAKTm93IHdlIGNhbiBsaW1pdCBvdXIgdmlzdWFsaXNhdGlvbnMganVzdCB0byB0aGlzIHJlZ2lvbiBieSBzcGVjaWZ5aW5nIHRoZSBuYW1lIG9mIHRoZSBuZXcgRk9WIGFzIGFuICJmb3YiIGFyZ3VlbWVudC4gCgpBcyB3ZSBhcmUgem9vbWluZyBpbiBjbG9zZXIgdG8gdGhlIHRpc3N1ZSwgd2UgY2FuIGFsc28gc3dpdGNoIGZyb20gcGxvdHRpbmcgY2VsbCBjZW50cm9pZHMgKGkuZS4gZG90cykgYnkgZGVmYXVsdCB0byB2aXN1YWxpc2luZyBjZWxsIHNlZ21lbnRhdGlvbiBib3VuZGFyaWVzLiBQbG90dGluZyBjZWxsIGJvdW5kYXJ5IHBvbHlnb25zIGZvciBsYXJnZSBGT1ZzIGNhbiBiZSBxdWl0ZSB0aW1lIGNvbnN1bWluZywgYW5kIGRvZXNuJ3QgcHJvdmlkZSBtdWNoIG1vcmUgZGV0YWlsIG9uIGEgZnVsbHkgem9vbWVkLW91dCB2aWV3LiAKCgpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04fQpJbWFnZURpbVBsb3Qoc2V1cmF0LCBmb3Y9IlJPSTEiLCBib3VuZGFyaWVzPSJzZWdtZW50YXRpb24iLCBib3JkZXIuY29sb3IgPSAiYmxhY2siICkKYGBgCldlIGNhbiB2aXN1YWxpc2UgZ2VuZSBleHByZXNzaW9uIG9yIG90aGVyIGNvbnRpbm91cyB2YXJpYWJsZSBvbiB0aGUgbmV3IEZPViBhcyBiZWZvcmUuCgpGb3IgZXhhbXBsZSwgaGVyZSB3ZSBoYXZlIE1TNEExL0NEMjAgZXhwcmVzc2lvbiwgd2hpY2ggaXMgYSBCLUNlbGwgbWFya2VyLiBXZSBjYW4gc2VlIGl0IHF1aXRlIG5pY2VseSBsaW1pdGVkIHRvIHRoZSBseW1waG9pZCBmb2xsaWNsZS4gCmBgYHtyfQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdCwgIk1TNEExIiwgZm92PSJST0kxIiwgYm91bmRhcmllcz0ic2VnbWVudGF0aW9uIiAsIGJvcmRlci5jb2xvciA9ICJibGFjayIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKCldlIGNhbiBhbHNvIG92ZXJsYXkgdGhlIGNvb3JkaW5hdGVzIG9mIGluZGl2aWR1YWwgbW9sZWN1bGVzIHRvIHRoZSBwbG90LiBGb3IgZXhhbXBsZSwgaGVyZSB3ZSBhcmUgYWRkZWQgc29tZSBtb3JlIFQtY2VsbCBhbmQgQi1jZWxsIHNwZWNpZmljIG1hcmtlcnMuIAoKVGhpcyB2aXN1YWxpc2F0aW9uIGNhbiBiZSB1c2VmdWwgYmVjYXVzZSBtb2xlY3VsZXMgYXJlIHN0b3JlZCBpbmRlcGVuZGVudGx5IG9mIGNlbGxzIGFuZCBjZWxsIGJvdW5kYXJpZXMgaW4gU2V1cmF0LiBUaGVyZWZvcmUsIGlmIHRoZXJlIGFyZSByZWdpb25zIHdoZXJlIGNlbGwgc2VnbWVudGF0aW9uIGlzIG5vdCBnb29kLCBvciBpZiBjZWxscyB3ZXJlIGZpbHRlcmVkIG91dCBmcm9tIGNsdXN0ZXJpbmcgYW5hbHlzaXMgZHVlIHRvIHRoZWlyIGxvdyBxdWFsaXR5LCB0aGUgbW9sZWN1bGVzIHdpbGwgcmVtYWluIGFuZCBjYW4gc3RpbGwgYmUgdmlzdWFsaXNlZCB0aGlzIHdheS4KCkZvciBleGFtcGxlLCBoZXJlIHdlIGNhbiBzZWUgdGhlcmUgYXJlIGEgZmV3IG1vbGVjdWxlcyBvZiBDWENSNSBkZXRlY3RlZCBvdXRzaWRlIG9mIGNlbGx1bGFyIGJvdW5kYXJpZXMuIAoKYGBge3J9CkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0LCAiTVM0QTEiLCBmb3Y9IlJPSTEiLCBib3VuZGFyaWVzPSJzZWdtZW50YXRpb24iLCBtb2xlY3VsZXM9YygiQ1hDUjUiLCAiRk9YUDMiKSwgbW9scy5zaXplID0gLjUsIGJvcmRlci5jb2xvciA9ICJibGFjayIgKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKYGBgCioqQ2VsbCBUeXBlIElkZW50aWZpY2F0aW9uKioKCllvdSBjYW4gbWFudWFsbHkgYW5ub3RhdGUgeW91ciBjZWxsIGNsdXN0ZXJzLCBvciB5b3UgY2FuIGNsYXNzaWZ5IHRoZW0gdXNpbmcgYSByZWZlcmVuY2Ugc2luZ2xlLWNlbGwgZGF0YXNldC4gVGhpcyBwcm9jZXNzIGlzIHNpbXBsZXIgdGhhbiBmb3IgVmlzaXVtIGRhdGEgYmVjYXVzZSBvdXIgZGF0YSBpcyBhdCB0aGUgc2luZ2xlLWNlbGwgbGV2ZWwsIGVzdGFibGlzaGluZyBhIG9uZS10by1vbmUgcmVsYXRpb25zaGlwIHdpdGhvdXQgdGhlIG5lZWQgZm9yIHNwb3QgZGVjb252b2x1dGlvbi4KCkhvd2V2ZXIsIG91ciB0cmFuc2NyaXB0b21lIGlzIG1vcmUgbGltaXRlZCBoZXJlLCBhbmQgc29tZSBjZWxsIHR5cGVzIG1heSBub3QgYmUgd2VsbCByZXByZXNlbnRlZC4gQWRkaXRpb25hbGx5LCBvdXIgc2luZ2xlLWNlbGwgcmVmZXJlbmNlIG1pZ2h0IGJlIG1pc3Npbmcgc29tZSBjZWxsIHR5cGVzIHRoYXQgYXJlIG5vdCB3ZWxsIGNhcHR1cmVkIGJ5IGRyb3BsZXQtYmFzZWQgdGVjaG5vbG9naWVzIGJ1dCBhcmUgcHJlc2VudCBpbiBvdXIgdGlzc3VlIGRhdGEuCgpJbiB0aGlzIGV4YW1wbGUsIHdlIHdpbGwgdXNlIGEgc2luZ2xlLWNlbGwgcmVmZXJlbmNlIGRhdGFzZXQgdGhhdCB3ZSBwcmVwYXJlZCBlYXJsaWVyLgoKV2Ugd2lsbCBzdGFydCBieSByZWFkaW5nIGluIHRoZSBzZXVyYXQgUkRTIGZpbGUuCmBgYHtyfQpyZWYgPC0gcmVhZFJEUygiL3Byb2plY3Qvc2hhcmVkL3NwYXRpYWxfZGF0YV9jYW1wL2RhdGFzZXRzL1NJTkdMRV9DRUxMX1JFRkVSRU5DRVMvQ09MT05fSENfNUtfQ0VMTFMuUkRTIikKYGBgCgpFeGFtaW5lIHRoZSBvYmplY3Q6CmBgYHtyfQpyZWYKYGBgCkFuZCBwbG90IHRoZSBwcmUtY29tcHV0ZWQgY2VsbCBjbHVzdGVycy4gV2UgY2FuIHNlZSB0aGF0IGhlcmUgd2UgaGF2ZSBxdWl0ZSBoaWdoIGxldmVsIGFubm90YXRpb24uIApgYGB7cn0KRGltUGxvdChyZWYpCmBgYApXZSB3YW50IHRvIGV2YWx1YXRlIGhvdyBtdWNoIHN0cnVjdHVyYWwgaW5mb3JtYXRpb24gaXMgbG9zdCBpbiBzaW5nbGUtY2VsbCBkYXRhIHdoZW4gbGltaXRpbmcgb3Vyc2VsdmVzIHRvIHRoZSB0YXJnZXRlZCBnZW5lIHNldC4gQWNjdXJhdGUgY2x1c3RlciBwcmVkaWN0aW9uIGlzIGNoYWxsZW5naW5nIGlmIHRoZSBjdXJyZW50IGdlbmUgc2V0IGRvZXMgbm90IGFkZXF1YXRlbHkgaWRlbnRpZnkgdGhlbS4gVG8gZG8gdGhpcywgd2Ugd2lsbCBxdWlja2x5IHJlLWVtYmVkZCB0aGUgZGF0YSB1c2luZyBvbmx5IHRoZSBnZW5lcyBwcmVzZW50IGluIG91ciBzcGF0aWFsIHRyYW5zY3JpcHRvbWljcyBkYXRhIGFuZCBrZWVwIHRoZSBvcmlnaW5hbCBjbHVzdGVyIGFubm90YXRpb25zIGRlcml2ZWQgZnJvbSB1bmJpYXNlZCBkYXRhLgoKSW4gdGhpcyBleGFtcGxlLCB3ZSBjYW4gb2JzZXJ2ZSB0aGF0IHRoZSBsaW1pdGVkIGdlbmUgc2V0IGRvZXMgYSByZWFzb25hYmx5IGdvb2Qgam9iIGF0IGRpc3Rpbmd1aXNoaW5nIG1ham9yIGNlbGwgcG9wdWxhdGlvbnMuIEhvd2V2ZXIsIGl0IHN0cnVnZ2xlcyB0byBkaWZmZXJlbnRpYXRlIGJldHdlZW4gc2ltaWxhciBjZWxsIHR5cGVzLCBzdWNoIGFzIG15b2ZpYnJvYmxhc3RzIGFuZCBmaWJyb2JsYXN0cywgYXMgZWZmZWN0aXZlbHkgYXMgYmVmb3JlLgoKYGBge3J9CnJlZiA8LSBTQ1RyYW5zZm9ybShyZWYsIHJlc2lkdWFsLmZlYXR1cmVzID1yb3duYW1lcyhzZXVyYXRfQ1JDMikpCnJlZiA8LSBSdW5QQ0EocmVmKQpyZWYgPC0gUnVuVU1BUChyZWYsIGRpbXM9MToyMCkKRGltUGxvdChyZWYsIGxhYmVsPVQsIHJlcGVsPVQpCmBgYApJZiB3ZSB2aXN1YWxpc2UgdGhlIHNwZWNpZmljaXR5IG9mIHRoZSBnZW5lIHBhbmVsIGFjcm9zcyBvdXIgc2luZ2xlIGNlbGwgcmVmZXJlbmNlIGNsdXN0ZXJzLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIHBhbmVsIGNvdmVyYWdlIGlzIG1haW5seSBjb25jZW50cmF0ZWQgYWNyb3NzIGVwaXRoZWxpYWwgY2VsbHMgYW5kIFQtQ2VsbHMgYW5kIG90aGVyIGltbXVuZSBjZWxscywgd2l0aCBmZXcgc3BlY2lmaWMgbWFya2VycyBleHByZXNzZWQgYnkgc3Ryb21hbCBjZWxscy4gCmBgYHtyfQpwcyA8LSBBZ2dyZWdhdGVFeHByZXNzaW9uKHJlZiwgZmVhdHVyZXMgPSByb3duYW1lcyhzZXVyYXQpLCBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJMb2dOb3JtYWxpemUiLCBhc3NheXM9IlJOQSIsIHJldHVybi5zZXVyYXQgPSBUKQpwcyA8LSBTY2FsZURhdGEocHMsIGZlYXR1cmVzPXJvd25hbWVzKHBzKSkKcGhlYXRtYXAoTGF5ZXJEYXRhKHBzLCBsYXllcj0ic2NhbGUuZGF0YSIpLCBzaG93X3Jvd25hbWVzID0gRikKYGBgCgoKTmV4dCwgd2UgY2FuIHVzZSB0aGUgc3RhbmRhcmQgU2V1cmF0IGludGVncmF0aW9uIGFuZCBjcm9zcy1jbGFzc2lmaWNhdGlvbiB3b3JrZmxvdyB0byB0cmFuc2ZlciBzaW5nbGUtY2VsbCBkZXJpdmVkIGxhYmVscyB0byBvdXIgc3BhdGlhbCBvYmplY3QuCgpCcmllZmx5LCB0aGUgZmlyc3QgZnVuY3Rpb24gaWRlbnRpZmllcyBhbmNob3JzIGJldHdlZW4gdGhlIHJlZmVyZW5jZSBzaW5nbGUtY2VsbCBkYXRhc2V0IChyZWYpIGFuZCB0aGUgcXVlcnkgc3BhdGlhbCBkYXRhc2V0IChzZXVyYXQpLiBBbmNob3JzIGFyZSBwYWlycyBvZiBjZWxscyB0aGF0IGFyZSBjb25zaWRlcmVkIHNpbWlsYXIgYmV0d2VlbiB0aGUgZGF0YXNldHMuIFRoZSAqbm9ybWFsaXphdGlvbi5tZXRob2QgPSAiU0NUIiogc3BlY2lmaWVzIHRoYXQgKlNDVHJhbnNmb3JtKiBub3JtYWxpemF0aW9uIHNob3VsZCBiZSB1c2VkLgoKVGhlIHNlY29uZCBzdGVwIHRyYW5zZmVycyB0aGUgY2VsbCB0eXBlIGxhYmVscyBmcm9tIHRoZSByZWZlcmVuY2UgZGF0YXNldCB0byB0aGUgcXVlcnkgZGF0YXNldC4gVGhlIGFuY2hvcnNldCBhcmd1bWVudCBzcGVjaWZpZXMgdGhlIGFuY2hvcnMgZm91bmQgaW4gdGhlIHByZXZpb3VzIHN0ZXAuIFRoZSAqcmVmZGF0YSA9IHJlZiRDZWxsVHlwZSogYXJndW1lbnQgc3BlY2lmaWVzIHRoZSBjZWxsIHR5cGUgbGFiZWxzIGZyb20gdGhlIHJlZmVyZW5jZSBkYXRhc2V0IHRvIGJlIHRyYW5zZmVycmVkLiBUaGUgKnByZWRpY3Rpb24uYXNzYXkgPSBUUlVFKiBhcmd1bWVudCBpbmRpY2F0ZXMgdGhhdCB0aGUgdHJhbnNmZXJyZWQgbGFiZWxzIHNob3VsZCBiZSBzdG9yZWQgaW4gYSBuZXcgYXNzYXkgaW4gdGhlIHF1ZXJ5IGRhdGFzZXQuIFRoZSAqd2VpZ2h0LnJlZHVjdGlvbiA9IHNldXJhdFtbInBjYSJdXSogYXJndW1lbnQgc3BlY2lmaWVzIHRoZSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gdG8gYmUgdXNlZCBmb3Igd2VpZ2h0aW5nIHRoZSB0cmFuc2ZlciwgYW5kICpkaW1zID0gMTozMCogc3BlY2lmaWVzIHRoZSBudW1iZXIgb2YgZGltZW5zaW9ucyB0byB1c2UuCgoKYGBge3J9CmFuY2hvcnMgPC0gRmluZFRyYW5zZmVyQW5jaG9ycyhyZWZlcmVuY2UgPSByZWYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVlcnkgPSBzZXVyYXRfQ1JDMiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJTQ1QiKQoKc2V1cmF0X0NSQzIgPC0gVHJhbnNmZXJEYXRhKGFuY2hvcnNldCA9IGFuY2hvcnMsIAogICAgICAgICAgICAgICAgICAgICAgIHJlZmRhdGEgPSByZWYkQ2VsbFR5cGUsIAogICAgICAgICAgICAgICAgICAgICAgIHByZWRpY3Rpb24uYXNzYXkgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgIHdlaWdodC5yZWR1Y3Rpb24gPSBzZXVyYXRfQ1JDMltbInBjYSJdXSwgCiAgICAgICAgICAgICAgICAgICAgICAgcXVlcnkgPSBzZXVyYXRfQ1JDMiwgCiAgICAgICAgICAgICAgICAgICAgICAgZGltcz0xOjMwKQoKYGBgCgpVbmZvcnR1bmF0ZWx5LCB0aGUgcHJlZGljdGVkIGxhYmVscyBhbmQgc3BhdGlhbCBjbHVzdGVycyBkbyBub3QgY29ycmVzcG9uZCBjbGVhcmx5IGluIGFsbCBjYXNlcy4gVGhpcyBkaXNjcmVwYW5jeSBpcyBwYXJ0aWN1bGFybHkgZXZpZGVudCBpbiB0aGUgbWlkZGxlIHJlZ2lvbnMgb2YgdGhlIFVNQVAsIHdoZXJlIG1hbnkgY2VsbHMgYXJlIHByZWRpY3RlZCBhcyBlcGl0aGVsaWFsIGNlbGxzIC0gcHJvYmFibHkgaW5jb3JyZWN0bHkhCgpIb3cgdG8gaW1wcm92ZSB0aGlzPwoKKipFbnN1cmUgR29vZCBSZXByZXNlbnRhdGlvbiBvZiBDZWxsIFR5cGUgTWFya2VycyBpbiAqaW4gc2l0dSogVGFyZ2V0IFBhbmVsKioKTW9zdCBjcml0aWNhbGx5LCBiZWZvcmUgdW5kZXJ0YWtpbmcgYW55IGV4cGVyaW1lbnRzIHlvdSB3YW50IHRvIGVuc3VyZSB0aGF0IHRoZXJlIGlzIGdvb2QgcmVwcmVzZW50YXRpb24gb2YgYWxsIGNlbGwgdHlwZXMgaW4geW91ciB0YXJnZXQgcGFuZWwgLSBpbiB0aGlzIGNhc2UsIHRoZXJlIGlzIG5vdCBtdWNoIHRvIGJlIGRvbmUgYXMgdGhlIGRhdGEgaGFzIGFscmVhZHkgYmVlbiBnZW5lcmF0ZWQuIAoKKipSZXZpZXcgYW5kIFJlZmluZSBSZWZlcmVuY2UgRGF0YToqKgpFbnN1cmUgdGhhdCB0aGUgcmVmZXJlbmNlIHNpbmdsZS1jZWxsIGRhdGFzZXQgaXMgY29tcHJlaGVuc2l2ZSBhbmQgYWNjdXJhdGVseSBhbm5vdGF0ZWQuIElmIGNlcnRhaW4gY2VsbCB0eXBlcyBhcmUgbm90IHdlbGwgcmVwcmVzZW50ZWQgb3IgYW5ub3RhdGVkIGluIHRoZSByZWZlcmVuY2UgZGF0YXNldCwgaXQgY2FuIGxlYWQgdG8gbWlzY2xhc3NpZmljYXRpb24uCgoqKkluY3JlYXNlIHRoZSBOdW1iZXIgb2YgRGltZW5zaW9uczoqKgpJbmNyZWFzaW5nIHRoZSBudW1iZXIgb2YgZGltZW5zaW9ucyB1c2VkIGluIHRoZSBVTUFQIGFuZCBQQ0Egc3RlcHMgbWlnaHQgY2FwdHVyZSBtb3JlIHZhcmlhbmNlIGluIHRoZSBkYXRhLCBsZWFkaW5nIHRvIGJldHRlciBsYWJlbCB0cmFuc2Zlci4KCioqRmlsdGVyIGFuZCBQcmVwcm9jZXNzIERhdGE6KioKRmlsdGVyaW5nIG91dCBsb3ctcXVhbGl0eSBjZWxscyBvciBnZW5lcyBhbmQgcGVyZm9ybWluZyBhZGRpdGlvbmFsIHByZXByb2Nlc3Npbmcgc3RlcHMgY2FuIGVuaGFuY2UgdGhlIGFjY3VyYWN5IG9mIHRoZSB0cmFuc2ZlciBhbmNob3JzIGFuZCwgY29uc2VxdWVudGx5LCB0aGUgbGFiZWwgcHJlZGljdGlvbnMuIAoKKipNYW51YWxseSBBbm5vdGF0ZSBvciBDb3JyZWN0IFByZWRpY3Rpb25zOioqCkluIGNhc2VzIHdoZXJlIGF1dG9tYXRpYyBsYWJlbCB0cmFuc2ZlciBpcyBpbnN1ZmZpY2llbnQsIGNvbnNpZGVyIG1hbnVhbGx5IGFubm90YXRpbmcgb3IgY29ycmVjdGluZyB0aGUgcHJlZGljdGlvbnMgZm9yIGNyaXRpY2FsIHJlZ2lvbnMgdG8gZW5zdXJlIGFjY3VyYWN5LgoKCmBgYHtyfQpEaW1QbG90KHNldXJhdF9DUkMyLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWQiLGxhYmVsID0gVCkKRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJwcmVkaWN0ZWQuaWQuc2NvcmUiKQpgYGAKQXMgYmVmb3JlLCB3ZSBjYW4gYWxzbyB2aXN1YWxpc2UgdGhlIHByZWRpY3RlZCBjZWxsIGxhYmVscyBpbiB0aXNzdWUgc3BhY2UuCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZCIpCmBgYApJbiBsaW5lIHdpdGggbm9uLXNwZWNpZmljIHByZWRpY3Rpb25zLCB3ZSBjYW4gYWxzbyBzZWUgdGhhdCB0aGUgcHJlZGljdGlvbiBzY29yZSBhY3Jvc3MgdGhlc2UgYXJlYXMgaXMgbG93ZXIuIAoKT3V0c2lkZSBvZiBzdHJvbWFsIGNlbGxzLCB3ZSBjYW4gYWxzbyBzZWUgdGhhdCBwcmVkaWN0aW9uIHByb2JhYmlsaXR5IGNhbiBiZSBsb3cgaW4gY2VsbHMgdGhhdCBlbWJlZGQgImJldHdlZW4iIGNsdXN0ZXJzLCBmb3IgZXhhbXBsZSBiZXR3ZWVuIGNvcmUgVC1DZWxscyBhbmQgQi1DZWxscywgdHdvIHBvcHVsYXRpb25zIHRoYXQgc2hvdWxkIGJlIGRpc3RpbmN0LiAKClRoaXMgaXMgb2Z0ZW4gdGhlIGNhc2Ugd2hlcmUgY2VsbCBzZWdtZW50YXRpb24gaXMgaW1wZXJmZWN0IGFuZCBwYXJ0aXRpb25zIHRyYW5zY3JpcHRzIGluIHN1Y2ggYSB3YXkgdGhhdCBpdCBnZW5lcmF0ZXMgImFydGlmaWNpYWwiIGRvdWJsZXRzIGJ5IHB1bGxpbmcgaW4gdHJhbnNjcmlwdHMgZnJvbSBhbiBhZGphY2VudCBjZWxsLiAKYGBge3J9CkZlYXR1cmVQbG90KHNldXJhdCwgInByZWRpY3RlZC5pZC5zY29yZSIpCmBgYApGb3IgZXhhbXBsZSwgaWYgd2UgdmlzdWFsaXNlIHRoZSBsaW5lYWdlIG1hcmtlcnMgZm9yIFQtQ2VsbHMgYW5kIEItQ2VsbHMsIHdlIGNhbiBzZWUgdGhhdCB0aGV5IGFyZSBvZnRlbiAiY28tZXhwcmVzc2VkIiBpbiB0aGUgc2FtZSBjZWxscyB3aGVuIGJpb2xvZ2ljYWxseSwgdGhleSBzaG91bGQgbm90IGJlLiAKClRoZSAqRmVhdHVyZVNjYXR0ZXIqIGZ1bmN0aW9uIGluIFNldXJhdCBpcyB1c2VkIHRvIGNyZWF0ZSBhIHNjYXR0ZXIgcGxvdCBzaG93aW5nIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgZXhwcmVzc2lvbiBsZXZlbHMgb2YgdHdvIGdlbmVzIGFjcm9zcyBhbGwgY2VsbHMuIFRoaXMgdmlzdWFsaXphdGlvbiBoZWxwcyB0byBpZGVudGlmeSBwb3RlbnRpYWwgY29ycmVsYXRpb25zIG9yIHBhdHRlcm5zIGJldHdlZW4gdGhlIHR3byBnZW5lcy4KCgpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMH0KRmVhdHVyZVNjYXR0ZXIoc2V1cmF0X0NSQzIsICJNUzRBMSIsICJDRDNEIiwgaml0dGVyPVQpCkZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCBjKCJNUzRBMSIsICJDRDNEIikpCmBgYAoKYGBge3IgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OH0KSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMyLCAgYm91bmRhcmllcz0ic2VnbWVudGF0aW9uIiwgYm9yZGVyLmNvbG9yID0gImJsYWNrIiApCmBgYAoKCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIpCmBgYAoKYGBge3J9CkltYWdlRGltUGxvdChzZXVyYXRfQ1JDMiwgZ3JvdXAuYnkgPSAicHJlZGljdGVkLmlkIikKYGBgCioqU3BhdGlhbCBOZWlnaGJvdXJob29kIEFuYWx5aXMqKgoKYGBge3J9CmNvb3JkcyA8LSBHZXRUaXNzdWVDb29yZGluYXRlcyhzZXVyYXRfQ1JDMiwgd2hpY2ggPSAiY2VudHJvaWRzIikKcm93bmFtZXMoY29vcmRzKSA8LSBjb29yZHMkY2VsbApuZWlnaGJvdXJzIDwtIEZpbmROZWlnaGJvcnMoYXMubWF0cml4KGNvb3Jkc1ssIGMoIngiLCAieSIpXSksIGsucGFyYW0gPSAyMCwgcmV0dXJuLm5laWdoYm9yPVRSVUUpCgpgYGAKQ29tcHV0aW5nIG5lYXJlc3QgbmVpZ2hib3JzCmBgYHtyfQpjZWxscyA8LSBXaGljaENlbGxzKHNldXJhdF9DUkMyLCBleHByZXNzaW9uPSBTQ1Rfc25uX3Jlcy4wLjIgPT0gMykKYWRqYWNlbnQgPC0gVG9wTmVpZ2hib3JzKG5laWdoYm91cnMsIGNlbGxzLCBuID0gMTApCgpJZGVudHMoc2V1cmF0X0NSQzIpIDwtICJPdGhlciBDZWxscyIKc2V1cmF0X0NSQzIgPC0gU2V0SWRlbnQoc2V1cmF0X0NSQzIsIGNlbGxzID0gYWRqYWNlbnQsICJBZGphY2VudCBDZWxscyIpCnNldXJhdF9DUkMyIDwtIFNldElkZW50KHNldXJhdF9DUkMyLCBjZWxscyA9IGNlbGxzLCAiQ2VsbHMgb2YgSW50ZXJlc3QiKQoKSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMyKQoKc2V1cmF0X0NSQzJbWyJncm91cDEiXV0gPC0gSWRlbnRzKHNldXJhdF9DUkMyKQpgYGAKKipGaW5kaW5nIFNwYXRpYWxseSBDb3JyZWxhdGVkIEdlbmVzKioKYGBge3J9Cm5laWdoYm91cnMgPC0gRmluZE5laWdoYm9ycyhhcy5tYXRyaXgoY29vcmRzWywgYygieCIsICJ5IildKSwgay5wYXJhbSA9IDUwKQptdCA8LSBMYXllckRhdGEoc2V1cmF0X0NSQzIsIGxheWVyID0gImNvdW50cyIsIGFzc2F5ID0gIlhFTklVTSIpCnN1bV9tdHggPC0gYXMubWF0cml4KG5laWdoYm91cnMkbm4gJSolIHQobXQpKQoKYGBgCgpXZSBjYW4gc3RvcmUgdGhlIG5laWdoYm91cmhvb2QtYWdncmVnYXRlZCB2YWx1ZXMgaW4gb3VyIFNldXJhdCBvYmplY3QgYXMgYSBzZXBhcmF0ZSBhc3NheSwgd2hpY2ggd2Ugd2lsbCBjYWxsICJORUlHSEJPVVJIT09ENTAiLiBXZSB0aGVuIG5vcm1hbGlzZSB0aGUgbWF0cml4LiAKYGBge3J9CnNldXJhdF9DUkMyW1siTkVJR0hCT1VSSE9PRDUwIl1dIDwtIENyZWF0ZUFzc2F5T2JqZWN0KHQoc3VtX210eCkpCnNldXJhdF9DUkMyIDwtIE5vcm1hbGl6ZURhdGEoc2V1cmF0X0NSQzIsIGFzc2F5ID0gIk5FSUdIQk9VUkhPT0Q1MCIpCgpgYGAKV2UgY2FuIHRoZW4gYXBwbHkgcXVpY2sgY29ycmVsYXRpb24gY2FsY3VsYXRpb25zIHRvIGlkZW50aWZ5IHNwYXRpYWxseSBjb3JyZWxhdGVkIGZlYXR1cmVzLiAKCgpgYGB7cn0KY29ycmdlbmVzIDwtIGNvcihhcy5tYXRyaXgodChMYXllckRhdGEoc2V1cmF0X0NSQzIsIGFzc2F5ID0gIk5FSUdIQk9VUkhPT0Q1MCIsIGxheWVyID0gImRhdGEiKSkpKQpkaWFnKGNvcnJnZW5lcykgPC0gMApoaWdoX2NvcnJfZ2VuZXMgPC0gd2hpY2gocm93TWF4cyhjb3JyZ2VuZXMpID4gLjcpCmRpYWcoY29ycmdlbmVzKSA8LSAxCmhlYXRtYXAgPC0gcGhlYXRtYXAoY29ycmdlbmVzW2hpZ2hfY29ycl9nZW5lcywgaGlnaF9jb3JyX2dlbmVzXSwgYm9yZGVyX2NvbG9yID0gTkEpCmBgYApgYGB7cn0KbW9kdWxlcyA8LSBjdXRyZWUoaGVhdG1hcCR0cmVlX3JvdywgNSkKbW9kdWxlcwpgYGAKTGV0cyB2aXN1YWxpemUgc29tZSBvZiB0aGUgZGV0ZWN0ZWQgc3BhdGlhbGx5IGNvLWxvY2FsaXppbmcgZ2VuZXMuIEZvciBleGFtcGxlLCAgbW9kdWxlIDIgZ2VuZXMgLSB3ZSBjYW4gc2VlIHRoYXQgQ0VBQ0FNNiBhbmQgQVFQOCBhcmUgc3BhdGlhbGx5IHNpbWlsYXIsIGJ1dCBub3QgbmVjZXNzYXJpbHkgYWx3YXlzIGV4cHJlc3NlZCBieSB0aGUgc2FtZSBjZWxscy4KYGBge3J9CkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJBS1I3QTMiKSArIHNjYWxlX2ZpbGxfdmlyaWRpc19jKCkKSW1hZ2VGZWF0dXJlUGxvdChzZXVyYXRfQ1JDMiwgIkMxUUJQIikgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJDRDI0IikgKyBzY2FsZV9maWxsX3ZpcmlkaXNfYygpCkltYWdlRmVhdHVyZVBsb3Qoc2V1cmF0X0NSQzIsICJBTlhBMSIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKCmBgYHtyfQpzZXVyYXRfQ1JDMiA8LSBBZGRNb2R1bGVTY29yZShzZXVyYXRfQ1JDMiwgZmVhdHVyZXM9c3BsaXQobmFtZXMobW9kdWxlcyksIG1vZHVsZXMpLCBhc3NheSA9ICJTQ1QiLCBuYmluPTMsIG5hbWUgPSAiTU9EIiApCmBgYApWaXN1YWxpc2luZyBtb2R1bGUgc2NvcmVzIC0gd2UgY2FuIHNlZSB0aGF0IHdlIGhhdmUgaWRlbnRpZmllZCBhIGdyb3VwIG9mIGdlbmVzIGNvLWxvY2FsaXNpbmcgYXQgdGhlIGJhc2Ugb2YgdGhlIGVwaXRoZWxpYWwgY3J5cHRzIChNT0QxKSBhbmQgYW5vdGhlciBtb2R1bGUgb2YgZ2VuZXMgY28tbG9jYWxpc2luZyBpbiBseW1waG9pZCBmb2xsaWNsZXMuCmBgYHtyfQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiTU9EMSIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiTU9EMiIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiTU9EMyIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiTU9ENCIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpJbWFnZUZlYXR1cmVQbG90KHNldXJhdF9DUkMyLCAiTU9ENSIpICsgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQpgYGAKKipEZXRlY3RpbmcgQ2VsbHVsYXIgTmljaGVzKioKYGBge3J9Cm5laWdoYm91cnMgPC0gRmluZE5laWdoYm9ycyhhcy5tYXRyaXgoY29vcmRzWywgYygieCIsICJ5IildKSwgay5wYXJhbSA9IDEwMCkKZGlhZyhuZWlnaGJvdXJzJG5uKSA8LSAwICMgZG9udCBjb3VudCB0cmFuc2NyaXB0b21lIG9mIHRoZSBjZWxsIGl0c2VsZiwganVzdCBuZWlnaGJvdXJzCm10IDwtIExheWVyRGF0YShzZXVyYXRfQ1JDMiwgbGF5ZXIgPSAiY291bnRzIiwgYXNzYXkgPSAiWEVOSVVNIikKc3VtX210eCA8LSBhcy5tYXRyaXgobmVpZ2hib3VycyRubiAlKiUgdChtdCkpCmBgYAoKSG93IGlzIHRoaXMgdXNlZnVsPyBXZWxsLCBub3cgeW91IGNhbiBjbHVzdGVyIGNlbGxzIG5vdCBvbiB0aGVpciBnZW5lIGV4cHJlc3Npb24gdmFsdWVzLCBidXQgZ2VuZSBleHByZXNzaW9uIHZhbHVlcyBvZiBzdXJyb3VuZGluZyBjZWxscy4gVGhpcyBlZmZlY3RpdmVseSBwYXJ0aXRpb25zIGNlbGxzIG5vdCBiYXNlZCBvbiB0aGVpciBpZGVudGl0eSwgYnV0IG9uIHRoZWlyIG1pY3JvLWVudmlyb25tZW50IQpVc2luZyB0aGlzIGFwcHJvYWNoLCB5b3UgY2FuIGlkZW50aWZ5IHRpc3N1ZSBuaWNoZXMKCkFsdGVybmF0aXZlIGFwcHJvYWNoZXMgLSB5b3UgY291bGQgY291bnQgY2VsbCB0eXBlcyByYXRoZXIgdGhhbiBnZW5lIGV4cHJlc3Npb24gdmFsdWVzLCBidXQgdGhhdCByZXF1aXJlcyB5b3UgdG8gaGF2ZSBmaW5hbGlzZWQgY2VsbCBhbm5vdGF0aW9uIGZvciB5b3VyIGRhdGFzZXQsIHdoaWNoIGlzIG5vdCBpZGVhbC4gU28sIHdlIGRvIHVuYmlhc2VkIHRyYW5zY3JpcHRvbWljcyBhcHByb2FjaC4gCgoqKkhvdyB3b3VsZCB5b3UgcnVuIHRoaXMgd2l0aCBjZWxsIHR5cGVzPyoqCmBgYHtyfQpzZXVyYXRfQ1JDMltbIk5FSUdIQk9VUkhPT0QxMDAiXV0gPC0gQ3JlYXRlQXNzYXlPYmplY3QodChzdW1fbXR4KSkKRGVmYXVsdEFzc2F5KHNldXJhdF9DUkMyKSA8LSAiTkVJR0hCT1VSSE9PRDEwMCIKc2V1cmF0X0NSQzIgPC0gTm9ybWFsaXplRGF0YShzZXVyYXRfQ1JDMikKc2V1cmF0X0NSQzIgPC0gU2NhbGVEYXRhKHNldXJhdF9DUkMyLCBmZWF0dXJlcyA9IHJvd25hbWVzKHNldXJhdF9DUkMyKSkKc2V1cmF0X0NSQzIgPC0gUnVuUENBKHNldXJhdF9DUkMyLCBmZWF0dXJlcyA9IHJvd25hbWVzKHNldXJhdF9DUkMyKSkKc2V1cmF0X0NSQzIgPC0gRmluZE5laWdoYm9ycyhzZXVyYXRfQ1JDMiwgcmVkdWN0aW9uID0gInBjYSIsIGRpbXMgPSAxOjEwKQpzZXVyYXRfQ1JDMiA8LSBGaW5kQ2x1c3RlcnMoc2V1cmF0X0NSQzIsIHJlc29sdXRpb24gPSAwLjEsIGNsdXN0ZXIubmFtZSA9ICJOaWNoZXMiKQoKYGBgCkxldHMgdmlzdWFsaXNlIHRoZSBkZXRlY3RlZCAibmljaGVzIi4gV2UgY2FuIHNlZSB0aGF0IHdlIGhhdmUgYWNoaWV2ZWQgYSBjb2Fyc2UgcGFydGlvbmluZyBvZiB0aGUgY2VsbHMgaW50byBjcnlwdCB0b3AsIG1pZC1jcnlwdCBhbmQgY3J5cHQtYmFzZSByZWdpb25zLCBhcyB3ZWxsIGFzIHNlZ21lbnRpbmcgb3V0IGZvbGxpY2xlcyBhbmQgc3ViLW11Y29zYWwgc3Ryb21hLgoKKipIb3cgd291bGQgeW91IHR3ZWFrIHRoZSBhYm92ZSBhcHByb2FjaCB0byBnZW5lcmF0ZSBtb3JlIG9yIGxlc3MgZ3JhbnVsYXIgbmljaGVzPyoqCmBgYHtyfQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGdyb3VwLmJ5ID0gIk5pY2hlcyIpCmBgYApXZSBjYW4gdGFidWxhdGUgb3VyIGRldGVjdGVkIG5pY2hlcyB3aXRoIHByZWRpY3RlZCBjZWxsIHR5cGUgbGFiZWxzIChvciBjbHVzdGVycykgdG8gdmlzdWFsaXNlIGVucmljaG1lbnQgb2YgZGlmZmVyZW50IGNlbGwgdHlwZXMgYWNyb3NzIHNwYXRpYWwgbmljaGVzLiAKCkZvciBleGFtcGxlLCBhcyBjb3VsZCBiZSBleHBlY3RlZCwgVC1DZWxscyBhbmQgQi1DZWxscyBlbnJpY2ggaW4gTmljaGUgMiAoZm9sbGljdWxhcikuCgpgYGB7cn0KY29tcCA8LSB0YWJsZShzZXVyYXRfQ1JDMiROaWNoZXMsIHNldXJhdF9DUkMyJHByZWRpY3RlZC5pZCkKcGhlYXRtYXAoY29tcCwgc2NhbGU9InJvdyIpCmBgYAoKYGBge3J9CmNvbXAgPC0gdGFibGUoc2V1cmF0X0NSQzIkTmljaGVzLCBzZXVyYXRfQ1JDMiRTQ1Rfc25uX3Jlcy4wLjYpCnBoZWF0bWFwKGNvbXAsIHNjYWxlPSJyb3ciKQpgYGAKYGBge3J9CnNhdmVSRFMoc2V1cmF0X0NSQzIsICJzZXVyYXRfQ1JDMl9hbm5vdC5SRFMiKQpgYGAKCgpjaGFuZ2UgdGhlIGNvbG9yCmBgYHtyfQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeShzY2FsZXMpCmNlbGxfY29sb3VycyA8LSBjKCIjRjg3NjZEIiwgIiNEQjhFMDAiLCAiI0FFQTIwMCIsICIjNjRCMjAwIiwgIiMwMEJENUMiLCAiIzAwQzFBNyIsIAogICAgICAgICAgICAgICAgICAiIzAwQkFERSIsICIjMDBBNkZGIiwgIiNCMzg1RkYiLCAiI0VGNjdFQiIsICIjRkY2M0I2IikKbmFtZXMoY2VsbF9jb2xvdXJzKSAgPC0gYygiRXBpdGhlbGl1bSIsICJGaWJyb2JsYXN0cyIsICJULUNlbGxzIiwgICJNeW9maWJyb2JsYXN0cyIsICJNYWNyb3BoYWdlcyIsICJHbGlhIiwgIkVuZG90aGVsaXVtIiwgIlRlbG9jeXRlcyIsICJQbGFzbWEiLCAiQi1DZWxscyIsICJQZXJpY3l0ZXMiKQpuYW1lcyhjZWxsX2NvbG91cnMpIDwtIHNldXJhdF9DUkMyJHByZWRpY3RlZC5pZCAlPiUgdW5pcXVlKCkKSW1hZ2VEaW1QbG90KHNldXJhdF9DUkMyLCBjb2xzID0gY2VsbF9jb2xvdXJzLCBncm91cC5ieSA9ICJwcmVkaWN0ZWQuaWQiKQpJbWFnZURpbVBsb3Qoc2V1cmF0X0NSQzIsIGdyb3VwLmJ5ID0gInByZWRpY3RlZC5pZCIpCmBgYAoKYGBge3J9Cm1lcmdlZCA8LSByZWFkUkRTKCJDUkNfbWVyZ2UucmRzIikKCkNlbGxwcm9wUGxvdChzZXVyYXRfQ1JDMiwgZ3JvdXAuYnk9ICJwcmVkaWNpdGVkLmlkIiwgcHJvcC5pbj0iIikKYGBgCgoKCgoKCgo=